init
This commit is contained in:
@@ -0,0 +1,183 @@
|
||||
package parsers
|
||||
|
||||
import (
|
||||
"caatsm/internal/config"
|
||||
"caatsm/internal/domain"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
textPattern = regexp.MustCompile(`\(([A-Z]{3})(.*)\)`)
|
||||
validPriority = regexp.MustCompile(`^(SS|DD|FF|GG|KK)$`)
|
||||
validAddress = regexp.MustCompile(`^[A-Z]{8}$`)
|
||||
emptyLineRemove = regexp.MustCompile(`(?m)^\s*$`)
|
||||
)
|
||||
|
||||
type AFTNParser struct {
|
||||
BodyPattern *config.BodyConfig
|
||||
}
|
||||
|
||||
// Parse parses a generic AFTN message based on its type.
|
||||
func (p AFTNParser) Parse(text string) (interface{}, error) {
|
||||
config := config.GetMyConfig()
|
||||
|
||||
data := ParseBody(text, config)
|
||||
|
||||
switch data["type"] {
|
||||
case "ARR":
|
||||
return &domain.ARR{
|
||||
Category: data["type"],
|
||||
AircraftID: data["number"],
|
||||
SSRModeAndCode: data["ssr"],
|
||||
DepartureAirport: data["departure"],
|
||||
ArrivalAirport: data["arrival"],
|
||||
}, nil
|
||||
case "DEP":
|
||||
return &domain.DEP{
|
||||
AircraftID: data["number"],
|
||||
SSRModeAndCode: data["ssr"],
|
||||
DepartureAirport: data["departure"],
|
||||
DepartureTime: data["departure_time"],
|
||||
Destination: data["arrival"],
|
||||
}, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid message type")
|
||||
}
|
||||
}
|
||||
|
||||
// removeEmptyLines removes empty lines from a given text.
|
||||
func removeEmptyLines(text string) string {
|
||||
cleanedText := emptyLineRemove.ReplaceAllString(text, "")
|
||||
return strings.ReplaceAll(cleanedText, "\n\n", "\n")
|
||||
}
|
||||
|
||||
// ParseAFTN parses an AFTN message from raw text.
|
||||
func ParseAFTN(rawMessage string) (*domain.AFTN, error) {
|
||||
cleanedText := removeEmptyLines(rawMessage)
|
||||
lines := strings.Split(cleanedText, "\n")
|
||||
|
||||
if len(lines) < 4 {
|
||||
return nil, fmt.Errorf("invalid AFTN message format")
|
||||
}
|
||||
|
||||
header, err := parseHeader(lines[0])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
priorityAndSender, err := parsePriorityAndSender(lines[1])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
timeAndReceiver, err := parseTimeAndReceiver(lines[2])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
text, bodyType, err := parseText(strings.Join(lines[3:], "\n"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
bodyData, err := parseBodyData(text)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &domain.AFTN{
|
||||
Header: header,
|
||||
PriorityAndSender: priorityAndSender,
|
||||
TimeAndReceiver: timeAndReceiver,
|
||||
Text: text,
|
||||
Category: bodyType,
|
||||
BodyData: bodyData,
|
||||
ReceivedTime: time.Now(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// parseHeader parses the header line of an AFTN message.
|
||||
func parseHeader(line string) (domain.Header, error) {
|
||||
parts := strings.Fields(line)
|
||||
if len(parts) < 3 {
|
||||
return domain.Header{}, fmt.Errorf("invalid header format")
|
||||
}
|
||||
return domain.Header{
|
||||
StartSignal: parts[0],
|
||||
SendID: parts[1],
|
||||
SendTime: parts[2],
|
||||
}, nil
|
||||
}
|
||||
|
||||
// parsePriorityAndSender parses the priority and sender line of an AFTN message.
|
||||
func parsePriorityAndSender(line string) (domain.PriorityAndSender, error) {
|
||||
parts := strings.Fields(line)
|
||||
if len(parts) < 2 {
|
||||
return domain.PriorityAndSender{}, fmt.Errorf("invalid priority and sender format")
|
||||
}
|
||||
return domain.PriorityAndSender{
|
||||
Priority: parts[0],
|
||||
Sender: parts[1],
|
||||
}, nil
|
||||
}
|
||||
|
||||
// parseTimeAndReceiver parses the time and receiver line of an AFTN message.
|
||||
func parseTimeAndReceiver(line string) (domain.TimeAndReceiver, error) {
|
||||
parts := strings.Fields(line)
|
||||
if len(parts) < 2 {
|
||||
return domain.TimeAndReceiver{}, fmt.Errorf("invalid time and receiver format")
|
||||
}
|
||||
return domain.TimeAndReceiver{
|
||||
Time: parts[0],
|
||||
Receiver: parts[1],
|
||||
}, nil
|
||||
}
|
||||
|
||||
// parseText parses the text and extracts the body type from an AFTN message.
|
||||
func parseText(text string) (string, string, error) {
|
||||
match := textPattern.FindStringSubmatch(text)
|
||||
if len(match) > 1 {
|
||||
return match[0], match[1], nil
|
||||
}
|
||||
return "", "", fmt.Errorf("invalid text format")
|
||||
}
|
||||
|
||||
// parseBodyData parses the body data of an AFTN message.
|
||||
func parseBodyData(text string) (interface{}, error) {
|
||||
bodyParser := AFTNParser{}
|
||||
bodyData, err := bodyParser.Parse(text)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse body data: %w", err)
|
||||
}
|
||||
return bodyData, nil
|
||||
}
|
||||
|
||||
// ValidateAFTN validates the fields of an AFTN message.
|
||||
func ValidateAFTN(msg *domain.AFTN) error {
|
||||
if missingRequiredFields(msg) {
|
||||
return fmt.Errorf("invalid AFTN message: missing fields")
|
||||
}
|
||||
|
||||
if !validPriority.MatchString(msg.PriorityAndSender.Priority) {
|
||||
return fmt.Errorf("invalid priority code")
|
||||
}
|
||||
|
||||
if !validAddress.MatchString(msg.TimeAndReceiver.Receiver) || !validAddress.MatchString(msg.PriorityAndSender.Sender) {
|
||||
return fmt.Errorf("invalid address format")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// missingRequiredFields checks if required fields in an AFTN message are missing.
|
||||
func missingRequiredFields(msg *domain.AFTN) bool {
|
||||
return msg.PriorityAndSender.Priority == "" ||
|
||||
msg.TimeAndReceiver.Receiver == "" ||
|
||||
msg.PriorityAndSender.Sender == "" ||
|
||||
msg.Header.StartSignal == "" ||
|
||||
msg.Header.SendID == "" ||
|
||||
msg.Header.SendTime == ""
|
||||
}
|
||||
@@ -0,0 +1,194 @@
|
||||
package parsers
|
||||
|
||||
import (
|
||||
"caatsm/internal/config"
|
||||
"caatsm/internal/domain"
|
||||
"regexp"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
var _ = Describe("AFTN Parser", func() {
|
||||
var testConfig *config.Config
|
||||
|
||||
BeforeEach(func() {
|
||||
testConfig = &config.Config{
|
||||
Body: []config.BodyConfig{
|
||||
{
|
||||
Name: "ARR",
|
||||
Patterns: []config.PatternConfig{
|
||||
{
|
||||
Pattern: `^\((?P<type>[A-Z]{3})\-(?P<number>[A-Z0-9]+)\-(?P<ssr>[A-Z0-9]+)\-(?P<departure>[A-Z]{4})\-(?P<arrival>[A-Z]{4})\)$`,
|
||||
Expression: regexp.MustCompile(`^\((?P<type>[A-Z]{3})\-(?P<number>[A-Z0-9]+)\-(?P<ssr>[A-Z0-9]+)\-(?P<departure>[A-Z]{4})\-(?P<arrival>[A-Z]{4})\)$`),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "DEP",
|
||||
Patterns: []config.PatternConfig{
|
||||
{
|
||||
Pattern: `^\((?P<type>[A-Z]{3})\-(?P<number>[A-Z0-9]+)\-(?P<ssr>[A-Z0-9]+)\-(?P<departure>[A-Z]{4})\-(?P<departure_time>\d{4})\-(?P<arrival>[A-Z]{4})\)$`,
|
||||
Expression: regexp.MustCompile(`^\((?P<type>[A-Z]{3})\-(?P<number>[A-Z0-9]+)\-(?P<ssr>[A-Z0-9]+)\-(?P<departure>[A-Z]{4})\-(?P<departure_time>\d{4})\-(?P<arrival>[A-Z]{4})\)$`),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
config.SetMyConfig(testConfig)
|
||||
})
|
||||
|
||||
Describe("Parse", func() {
|
||||
It("should parse ARR messages correctly", func() {
|
||||
message := "(ARR-AB123-SSR1234-KJFK-KLAX)"
|
||||
parser := AFTNParser{}
|
||||
parsedMessage, err := parser.Parse(message)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(parsedMessage).To(BeAssignableToTypeOf(&domain.ARR{}))
|
||||
arrMessage := parsedMessage.(*domain.ARR)
|
||||
Expect(arrMessage.Category).To(Equal("ARR"))
|
||||
Expect(arrMessage.AircraftID).To(Equal("AB123"))
|
||||
Expect(arrMessage.SSRModeAndCode).To(Equal("SSR1234"))
|
||||
Expect(arrMessage.DepartureAirport).To(Equal("KJFK"))
|
||||
Expect(arrMessage.ArrivalAirport).To(Equal("KLAX"))
|
||||
})
|
||||
|
||||
It("should parse DEP messages correctly", func() {
|
||||
message := "(DEP-AB123-SSR1234-KJFK-1500-KLAX)"
|
||||
parser := AFTNParser{}
|
||||
parsedMessage, err := parser.Parse(message)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(parsedMessage).To(BeAssignableToTypeOf(&domain.DEP{}))
|
||||
depMessage := parsedMessage.(*domain.DEP)
|
||||
Expect(depMessage.AircraftID).To(Equal("AB123"))
|
||||
Expect(depMessage.SSRModeAndCode).To(Equal("SSR1234"))
|
||||
Expect(depMessage.DepartureAirport).To(Equal("KJFK"))
|
||||
Expect(depMessage.DepartureTime).To(Equal("1500"))
|
||||
Expect(depMessage.Destination).To(Equal("KLAX"))
|
||||
})
|
||||
|
||||
It("should return an error for invalid message types", func() {
|
||||
message := "(XYZ-AB123-SSR1234-KJFK-KLAX)"
|
||||
parser := AFTNParser{}
|
||||
_, err := parser.Parse(message)
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(err.Error()).To(Equal("invalid message type"))
|
||||
})
|
||||
})
|
||||
|
||||
Describe("ParseAFTN", func() {
|
||||
It("should parse a valid AFTN message", func() {
|
||||
rawMessage := `ZCZC TMQ2611 151524
|
||||
FF SENDERAA
|
||||
151524 RECEIVER
|
||||
(ARR-AB123-SSR1234-KJFK-KLAX)`
|
||||
|
||||
aftnMessage, err := ParseAFTN(rawMessage)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(aftnMessage).NotTo(BeNil())
|
||||
Expect(aftnMessage.Header.StartSignal).To(Equal("ZCZC"))
|
||||
Expect(aftnMessage.Header.SendID).To(Equal("TMQ2611"))
|
||||
Expect(aftnMessage.Header.SendTime).To(Equal("151524"))
|
||||
Expect(aftnMessage.Category).To(Equal("ARR"))
|
||||
})
|
||||
|
||||
It("should return an error for invalid AFTN message format", func() {
|
||||
rawMessage := `ZCZC
|
||||
TMQ2611
|
||||
151524`
|
||||
|
||||
_, err := ParseAFTN(rawMessage)
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(err.Error()).To(Equal("invalid AFTN message format"))
|
||||
})
|
||||
})
|
||||
|
||||
Describe("ValidateAFTN", func() {
|
||||
It("should validate a valid AFTN message", func() {
|
||||
aftnMessage := &domain.AFTN{
|
||||
Header: domain.Header{
|
||||
StartSignal: "ZCZC",
|
||||
SendID: "TMQ2611",
|
||||
SendTime: "151524",
|
||||
},
|
||||
PriorityAndSender: domain.PriorityAndSender{
|
||||
Priority: "FF",
|
||||
Sender: "SENDERAA",
|
||||
},
|
||||
TimeAndReceiver: domain.TimeAndReceiver{
|
||||
Time: "151524",
|
||||
Receiver: "RECEIVER",
|
||||
},
|
||||
Category: "ARR",
|
||||
}
|
||||
err := ValidateAFTN(aftnMessage)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
It("should return an error for missing required fields", func() {
|
||||
aftnMessage := &domain.AFTN{
|
||||
Header: domain.Header{
|
||||
StartSignal: "",
|
||||
SendID: "TMQ2611",
|
||||
SendTime: "151524",
|
||||
},
|
||||
PriorityAndSender: domain.PriorityAndSender{
|
||||
Priority: "FF",
|
||||
Sender: "",
|
||||
},
|
||||
TimeAndReceiver: domain.TimeAndReceiver{
|
||||
Time: "151524",
|
||||
Receiver: "RECEIVER",
|
||||
},
|
||||
Category: "ARR",
|
||||
}
|
||||
err := ValidateAFTN(aftnMessage)
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(err.Error()).To(Equal("invalid AFTN message: missing fields"))
|
||||
})
|
||||
|
||||
It("should return an error for invalid priority code", func() {
|
||||
aftnMessage := &domain.AFTN{
|
||||
Header: domain.Header{
|
||||
StartSignal: "ZCZC",
|
||||
SendID: "TMQ2611",
|
||||
SendTime: "151524",
|
||||
},
|
||||
PriorityAndSender: domain.PriorityAndSender{
|
||||
Priority: "ZZ",
|
||||
Sender: "SENDERAA",
|
||||
},
|
||||
TimeAndReceiver: domain.TimeAndReceiver{
|
||||
Time: "151524",
|
||||
Receiver: "RECEIVER",
|
||||
},
|
||||
Category: "ARR",
|
||||
}
|
||||
err := ValidateAFTN(aftnMessage)
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(err.Error()).To(Equal("invalid priority code"))
|
||||
})
|
||||
|
||||
It("should return an error for invalid address format", func() {
|
||||
aftnMessage := &domain.AFTN{
|
||||
Header: domain.Header{
|
||||
StartSignal: "ZCZC",
|
||||
SendID: "TMQ2611",
|
||||
SendTime: "151524",
|
||||
},
|
||||
PriorityAndSender: domain.PriorityAndSender{
|
||||
Priority: "FF",
|
||||
Sender: "INVALID",
|
||||
},
|
||||
TimeAndReceiver: domain.TimeAndReceiver{
|
||||
Time: "151524",
|
||||
Receiver: "RECEIVERAA",
|
||||
},
|
||||
Category: "ARR",
|
||||
}
|
||||
err := ValidateAFTN(aftnMessage)
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(err.Error()).To(Equal("invalid address format"))
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,39 @@
|
||||
package parsers
|
||||
|
||||
import (
|
||||
"caatsm/internal/config"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
var bodyTypePattern = regexp.MustCompile(`^\(([A-Z]{3})(.*\n?)+\)$`)
|
||||
|
||||
// FindPatterns detects the message body type based on the configuration.
|
||||
func FindPatterns(messageBody string, config *config.Config) *config.BodyConfig {
|
||||
if match := bodyTypePattern.FindStringSubmatch(messageBody); len(match) > 1 {
|
||||
name := match[1]
|
||||
for _, body := range config.Body {
|
||||
if body.Name == name {
|
||||
return &body
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ParseBody parses the message body and extracts the data based on the patterns defined in the configuration.
|
||||
func ParseBody(messageBody string, config *config.Config) map[string]string {
|
||||
if body := FindPatterns(messageBody, config); body != nil {
|
||||
for _, pattern := range body.Patterns {
|
||||
if matches := pattern.Expression.FindStringSubmatch(messageBody); matches != nil {
|
||||
result := make(map[string]string)
|
||||
for i, name := range pattern.Expression.SubexpNames() {
|
||||
if i != 0 && name != "" {
|
||||
result[name] = matches[i]
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package parsers
|
||||
|
||||
import (
|
||||
"caatsm/internal/config"
|
||||
"regexp"
|
||||
"testing"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
func TestParsers(t *testing.T) {
|
||||
RegisterFailHandler(Fail)
|
||||
RunSpecs(t, "Parsers Suite")
|
||||
}
|
||||
|
||||
var _ = Describe("Pattern Parser", func() {
|
||||
var testConfig *config.Config
|
||||
|
||||
BeforeEach(func() {
|
||||
testConfig = &config.Config{
|
||||
Body: []config.BodyConfig{
|
||||
{
|
||||
Name: "FPL",
|
||||
Patterns: []config.PatternConfig{
|
||||
{
|
||||
Pattern: `^\((?P<type>[A-Z]{3})-(?P<number>[A-Z0-9]+)\)$`,
|
||||
Expression: regexp.MustCompile(`^\((?P<type>[A-Z]{3})-(?P<number>[A-Z0-9]+)\)$`),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
Describe("FindPatterns", func() {
|
||||
It("should return the correct BodyConfig based on the message body", func() {
|
||||
message := "(FPL-AB123)"
|
||||
bodyConfig := FindPatterns(message, testConfig)
|
||||
Expect(bodyConfig).NotTo(BeNil())
|
||||
Expect(bodyConfig.Name).To(Equal("FPL"))
|
||||
})
|
||||
|
||||
It("should return nil if no pattern matches", func() {
|
||||
message := "(XYZ-123)"
|
||||
bodyConfig := FindPatterns(message, testConfig)
|
||||
Expect(bodyConfig).To(BeNil())
|
||||
})
|
||||
})
|
||||
|
||||
Describe("ParseBody", func() {
|
||||
It("should parse the message body and extract data based on patterns", func() {
|
||||
message := "(FPL-AB123)"
|
||||
parsedData := ParseBody(message, testConfig)
|
||||
Expect(parsedData).NotTo(BeNil())
|
||||
Expect(parsedData["type"]).To(Equal("FPL"))
|
||||
Expect(parsedData["number"]).To(Equal("AB123"))
|
||||
})
|
||||
|
||||
It("should return nil if no patterns match", func() {
|
||||
message := "(XYZ-123)"
|
||||
parsedData := ParseBody(message, testConfig)
|
||||
Expect(parsedData).To(BeNil())
|
||||
})
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user