✨ Add configuration for Code Review Automation and enhance .gitignore. Introduce .coderabbit.yml for automated reviews with profiles for correctness, maintainability, security, and performance. Update paths to include relevant directories and exclude generated files. Modify .gitignore to include coverage reports and generated files. Refactor Docker Compose to use updated paths for database initialization scripts. Update Go module dependencies and enhance Makefile with new code generation tasks. Transition domain models to a new DTO structure for better separation of concerns.
This commit is contained in:
@@ -0,0 +1,52 @@
|
||||
package dto
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// MessageStatus represents the parsing status of a telegram.
|
||||
// It is intentionally decoupled from infrastructure concerns (e.g. DB or publish failures)
|
||||
// so that domain parsing state can be reasoned about independently.
|
||||
type MessageStatus string
|
||||
|
||||
const (
|
||||
MessageStatusUnknown MessageStatus = "unknown"
|
||||
MessageStatusParsed MessageStatus = "parsed"
|
||||
MessageStatusHeaderError MessageStatus = "header_error"
|
||||
MessageStatusBodyError MessageStatus = "body_error"
|
||||
)
|
||||
|
||||
// ParsedTelegram holds the parsed data from an aviation message.
|
||||
// It is a transport-oriented model used by the application pipeline (parser,
|
||||
// persistence, publishing), and may embed domain-specific body structures
|
||||
// (e.g. *domain.FPL, *domain.DEP) in BodyData.
|
||||
type ParsedTelegram struct {
|
||||
Uuid string `json:"uuid"`
|
||||
MessageID string `json:"messageId"`
|
||||
DateTime string `json:"dateTime"`
|
||||
PriorityIndicator string `json:"priorityIndicator"`
|
||||
PrimaryAddress string `json:"primaryAddress"`
|
||||
SecondaryAddresses string `json:"secondaryAddresses,omitempty"`
|
||||
Originator string `json:"originator,omitempty"`
|
||||
OriginatorDateTime string `json:"originatorDateTime,omitempty"`
|
||||
Category string `json:"category,omitempty"`
|
||||
Body string `json:"body"`
|
||||
Content string `json:"content,omitempty"`
|
||||
BodyData interface{} `json:"bodyData,omitempty"`
|
||||
ReceivedAt time.Time `json:"receivedAt"`
|
||||
ParsedAt time.Time `json:"parsedAt,omitempty"`
|
||||
DispatchedAt time.Time `json:"dispatchedAt,omitempty"`
|
||||
NeedDispatch bool `json:"needDispatch"`
|
||||
Parsed bool `json:"parsed"`
|
||||
Comments string `json:"comments,omitempty"`
|
||||
Status MessageStatus
|
||||
ErrorReason string `json:"errorReason,omitempty"`
|
||||
}
|
||||
|
||||
// NewParsedTelegram initializes a ParsedTelegram with default values.
|
||||
func NewParsedTelegram() *ParsedTelegram {
|
||||
return &ParsedTelegram{
|
||||
Parsed: false,
|
||||
Status: MessageStatusUnknown,
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,13 @@
|
||||
package mapper
|
||||
|
||||
import "caatsm/internal/model"
|
||||
import "caatsm/internal/adapter/dto"
|
||||
|
||||
// Mapper defines the interface for mapping between pipeline models and database models
|
||||
type Mapper interface {
|
||||
// ToDBRow converts a ParsedTelegram to a database row representation
|
||||
ToDBRow(msg *model.ParsedTelegram) ([]interface{}, error)
|
||||
ToDBRow(msg *dto.ParsedTelegram) ([]interface{}, error)
|
||||
|
||||
// FromDBRow converts a database row to a ParsedTelegram
|
||||
FromDBRow(row []interface{}) (*model.ParsedTelegram, error)
|
||||
FromDBRow(row []interface{}) (*dto.ParsedTelegram, error)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package mapper
|
||||
|
||||
import (
|
||||
"caatsm/internal/model"
|
||||
"caatsm/internal/adapter/dto"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"time"
|
||||
@@ -18,7 +18,7 @@ func NewTelegramMapper() *TelegramMapper {
|
||||
}
|
||||
|
||||
// ToDBRow converts a ParsedTelegram to a database row representation
|
||||
func (m *TelegramMapper) ToDBRow(msg *model.ParsedTelegram) ([]interface{}, error) {
|
||||
func (m *TelegramMapper) ToDBRow(msg *dto.ParsedTelegram) ([]interface{}, error) {
|
||||
// Parse UUID
|
||||
var msgUUID uuid.UUID
|
||||
var err error
|
||||
@@ -45,7 +45,7 @@ func (m *TelegramMapper) ToDBRow(msg *model.ParsedTelegram) ([]interface{}, erro
|
||||
|
||||
status := msg.Status
|
||||
if status == "" {
|
||||
status = model.MessageStatusUnknown
|
||||
status = dto.MessageStatusUnknown
|
||||
}
|
||||
|
||||
return []interface{}{
|
||||
@@ -70,7 +70,7 @@ func (m *TelegramMapper) ToDBRow(msg *model.ParsedTelegram) ([]interface{}, erro
|
||||
}
|
||||
|
||||
// FromDBRow converts a database row to a ParsedTelegram
|
||||
func (m *TelegramMapper) FromDBRow(row []interface{}) (*model.ParsedTelegram, error) {
|
||||
func (m *TelegramMapper) FromDBRow(row []interface{}) (*dto.ParsedTelegram, error) {
|
||||
const expectedColumns = 17
|
||||
if len(row) < expectedColumns {
|
||||
return nil, fmt.Errorf("expected %d columns, got %d", expectedColumns, len(row))
|
||||
@@ -128,12 +128,12 @@ func (m *TelegramMapper) FromDBRow(row []interface{}) (*model.ParsedTelegram, er
|
||||
}
|
||||
}
|
||||
|
||||
status := model.MessageStatusUnknown
|
||||
status := dto.MessageStatusUnknown
|
||||
if rawStatus := toString(row[11]); rawStatus != "" {
|
||||
status = model.MessageStatus(rawStatus)
|
||||
status = dto.MessageStatus(rawStatus)
|
||||
}
|
||||
|
||||
return &model.ParsedTelegram{
|
||||
return &dto.ParsedTelegram{
|
||||
Uuid: msgUUID.String(),
|
||||
MessageID: toString(row[1]),
|
||||
DateTime: toString(row[2]),
|
||||
|
||||
@@ -3,7 +3,7 @@ package mapper
|
||||
import (
|
||||
"time"
|
||||
|
||||
"caatsm/internal/model"
|
||||
"caatsm/internal/adapter/dto"
|
||||
|
||||
"github.com/google/uuid"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
@@ -19,7 +19,7 @@ var _ = Describe("TelegramMapper", func() {
|
||||
|
||||
Describe("ToDBRow", func() {
|
||||
It("generates a UUID when missing", func() {
|
||||
msg := &model.ParsedTelegram{}
|
||||
msg := &dto.ParsedTelegram{}
|
||||
|
||||
row, err := mapper.ToDBRow(msg)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
@@ -33,7 +33,7 @@ var _ = Describe("TelegramMapper", func() {
|
||||
Describe("FromDBRow", func() {
|
||||
It("round-trips telegram data", func() {
|
||||
now := time.Now().UTC()
|
||||
original := &model.ParsedTelegram{
|
||||
original := &dto.ParsedTelegram{
|
||||
Uuid: uuid.NewString(),
|
||||
MessageID: "TMQ1324",
|
||||
DateTime: "150631",
|
||||
@@ -49,7 +49,7 @@ var _ = Describe("TelegramMapper", func() {
|
||||
ParsedAt: now,
|
||||
DispatchedAt: now,
|
||||
NeedDispatch: true,
|
||||
Status: model.MessageStatusParsed,
|
||||
Status: dto.MessageStatusParsed,
|
||||
}
|
||||
|
||||
row, err := mapper.ToDBRow(original)
|
||||
|
||||
@@ -0,0 +1,408 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"caatsm/internal/domain"
|
||||
"caatsm/internal/adapter/dto"
|
||||
"errors"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
const (
|
||||
SSR = "ssr"
|
||||
DepartureCode = "dep"
|
||||
DepartureTime = "dep_time"
|
||||
ArrivalCode = "arr"
|
||||
ArrivalTime = "arr_time"
|
||||
DestinationCode = "dest"
|
||||
OtherInfo = "other"
|
||||
|
||||
ReferenceData = "reference_data"
|
||||
Aircraft = "aircraft"
|
||||
CategorySurveillance = "surve"
|
||||
Indicator = "indicator"
|
||||
Other = "other"
|
||||
AircraftID = "aircraft"
|
||||
Surveillance = "surve"
|
||||
Speed = "speed"
|
||||
Level = "level"
|
||||
Route = "route"
|
||||
EstimatedTime = "estt"
|
||||
AlternateAirport = "alter"
|
||||
PBN = "pbn"
|
||||
NavigationEquipment = "nav"
|
||||
EstimatedElapsedTime = "eet"
|
||||
SELCALCode = "sel"
|
||||
PerformanceCategory = "per"
|
||||
RerouteInformation = "rif"
|
||||
Remarks = "remark"
|
||||
)
|
||||
|
||||
var (
|
||||
otherPatterns = []*regexp.Regexp{navPattern,
|
||||
remarkPattern,
|
||||
selPattern,
|
||||
pbnPattern,
|
||||
eetPattern,
|
||||
performancePattern,
|
||||
regPattern,
|
||||
reroutePattern}
|
||||
// ErrHeaderParse indicates an invalid header section.
|
||||
ErrHeaderParse = errors.New("invalid telegram header")
|
||||
// ErrBodyParse indicates a failure matching the telegram body.
|
||||
ErrBodyParse = errors.New("invalid telegram body")
|
||||
)
|
||||
|
||||
type BodyParser struct {
|
||||
body string
|
||||
bodyPatterns map[string]BodyConfig
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
func NewBodyParser(body string) *BodyParser {
|
||||
return &BodyParser{
|
||||
bodyPatterns: bodyPatterns,
|
||||
body: body,
|
||||
}
|
||||
}
|
||||
|
||||
func (parser *BodyParser) GetBodyPatterns() map[string]BodyConfig {
|
||||
parser.mu.Lock()
|
||||
defer parser.mu.Unlock()
|
||||
return parser.bodyPatterns
|
||||
}
|
||||
|
||||
func (parser *BodyParser) SetBodyPatterns(patterns map[string]BodyConfig) {
|
||||
parser.mu.Lock()
|
||||
defer parser.mu.Unlock()
|
||||
parser.bodyPatterns = patterns
|
||||
}
|
||||
|
||||
func (parser *BodyParser) Parse() (string, interface{}, error) {
|
||||
parser.mu.Lock()
|
||||
defer parser.mu.Unlock()
|
||||
|
||||
parser.body = strings.TrimSpace(parser.body)
|
||||
category := findCategory(parser.body)
|
||||
if category == "" {
|
||||
return "", nil, fmt.Errorf("no category found in body text")
|
||||
}
|
||||
|
||||
if patternConfig, exists := parser.bodyPatterns[category]; exists && patternConfig.Patterns != nil {
|
||||
for _, p := range patternConfig.Patterns {
|
||||
if data := extract(parser.body, p.Expression); data != nil {
|
||||
return parser.createBodyData(data)
|
||||
}
|
||||
}
|
||||
}
|
||||
return "", nil, fmt.Errorf("no matching pattern found for body: %s", parser.body)
|
||||
}
|
||||
|
||||
func findCategory(body string) string {
|
||||
if match := categoryRegex.FindStringSubmatch(body); match != nil {
|
||||
for i, name := range categoryRegex.SubexpNames() {
|
||||
if i != 0 && name == "category" {
|
||||
return match[i]
|
||||
}
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func extract(data string, exp *regexp.Regexp) map[string]string {
|
||||
match := exp.FindStringSubmatch(data)
|
||||
if len(match) > 0 {
|
||||
return extractData(match, exp)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func extractData(match []string, re *regexp.Regexp) map[string]string {
|
||||
data := make(map[string]string)
|
||||
for i, name := range re.SubexpNames() {
|
||||
if i != 0 && name != "" {
|
||||
data[name] = strings.TrimSpace(match[i])
|
||||
}
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
func (parser *BodyParser) createBodyData(data map[string]string) (string, interface{}, error) {
|
||||
switch category := data["category"]; category {
|
||||
case CategoryArrival:
|
||||
return category, &domain.ARR{
|
||||
Category: data[Category],
|
||||
AircraftID: data[FlightNumber],
|
||||
SSRModeAndCode: data[SSR],
|
||||
DepartureAirport: data[DepartureCode],
|
||||
ArrivalAirport: data[ArrivalCode],
|
||||
ArrivalTime: data[ArrivalTime],
|
||||
}, nil
|
||||
case CategoryDeparture:
|
||||
return category, &domain.DEP{
|
||||
Category: data[Category],
|
||||
AircraftID: data[FlightNumber],
|
||||
SSRModeAndCode: data[SSR],
|
||||
DepartureAirport: data[DepartureCode],
|
||||
DepartureTime: data[DepartureTime],
|
||||
Destination: data[ArrivalCode],
|
||||
}, nil
|
||||
case CategoryCancellation:
|
||||
return category, &domain.CNL{
|
||||
Category: data[category],
|
||||
AircraftID: data[FlightNumber],
|
||||
DepartureAirport: data[DepartureCode],
|
||||
DestinationAirport: data[ArrivalCode],
|
||||
}, nil
|
||||
case CategoryDelay:
|
||||
return category, &domain.DLA{
|
||||
Category: data[Category],
|
||||
AircraftID: data[FlightNumber],
|
||||
DepartureAirport: data[DepartureCode],
|
||||
NewDepartureTime: data[DepartureTime],
|
||||
ArrivalAirport: data[ArrivalCode],
|
||||
ArrivalTime: data[ArrivalTime],
|
||||
}, nil
|
||||
case CategoryFlightPlan:
|
||||
otherData := parseOther(data[OtherInfo])
|
||||
return category, &domain.FPL{
|
||||
Category: data[Category],
|
||||
FlightNumber: data[FlightNumber],
|
||||
ReferenceData: data[ReferenceData],
|
||||
AircraftID: data[AircraftID],
|
||||
SSRModeAndCode: data[Surveillance],
|
||||
FlightRulesAndType: data[Indicator],
|
||||
CruisingSpeedAndLevel: data[Speed] + data[Level],
|
||||
DepartureAirport: data[DepartureCode],
|
||||
DepartureTime: data[DepartureTime],
|
||||
Route: data[Route],
|
||||
DestinationAndTotalTime: data[DestinationCode] + data[EstimatedTime],
|
||||
AlternateAirport: data[AlternateAirport],
|
||||
OtherInfo: data[OtherInfo],
|
||||
Register: otherData[Register],
|
||||
EstimatedArrivalTime: data[EstimatedTime],
|
||||
PBN: otherData[PBN],
|
||||
NavigationEquipment: otherData[NavigationEquipment],
|
||||
EstimatedElapsedTime: otherData[EstimatedElapsedTime],
|
||||
SELCALCode: otherData[SELCALCode],
|
||||
PerformanceCategory: otherData[PerformanceCategory],
|
||||
RerouteInformation: otherData[RerouteInformation],
|
||||
Remarks: otherData[Remarks],
|
||||
}, nil
|
||||
default:
|
||||
return category, nil, fmt.Errorf("invalid message type: %s", category)
|
||||
}
|
||||
}
|
||||
|
||||
func Parse(rawText string) (*dto.ParsedTelegram, error) {
|
||||
header, err := ParseHeader(rawText)
|
||||
if err != nil {
|
||||
msg := dto.NewParsedTelegram()
|
||||
msg.Content = rawText
|
||||
msg.Comments = err.Error()
|
||||
msg.ErrorReason = err.Error()
|
||||
msg.Status = dto.MessageStatusHeaderError
|
||||
return msg, fmt.Errorf("%w: %w", ErrHeaderParse, err)
|
||||
}
|
||||
|
||||
bodyParser := NewBodyParser(header.Body)
|
||||
category, bodyData, bodyErr := bodyParser.Parse()
|
||||
header.Category = category
|
||||
header.ParsedAt = time.Now()
|
||||
|
||||
if bodyErr != nil {
|
||||
return &dto.ParsedTelegram{
|
||||
MessageID: header.MessageID,
|
||||
DateTime: header.DateTime,
|
||||
PriorityIndicator: header.PriorityIndicator,
|
||||
PrimaryAddress: header.PrimaryAddress,
|
||||
SecondaryAddresses: header.SecondaryAddresses,
|
||||
Originator: header.Originator,
|
||||
OriginatorDateTime: header.OriginatorDateTime,
|
||||
Category: header.Category,
|
||||
Body: header.Body,
|
||||
Content: header.Content,
|
||||
ReceivedAt: header.ReceivedAt,
|
||||
ParsedAt: header.ParsedAt,
|
||||
Parsed: false,
|
||||
Comments: bodyErr.Error(),
|
||||
Status: dto.MessageStatusBodyError,
|
||||
ErrorReason: bodyErr.Error(),
|
||||
}, fmt.Errorf("%w: %w", ErrBodyParse, bodyErr)
|
||||
}
|
||||
|
||||
parsed := &dto.ParsedTelegram{
|
||||
MessageID: header.MessageID,
|
||||
DateTime: header.DateTime,
|
||||
PriorityIndicator: header.PriorityIndicator,
|
||||
PrimaryAddress: header.PrimaryAddress,
|
||||
SecondaryAddresses: header.SecondaryAddresses,
|
||||
Originator: header.Originator,
|
||||
OriginatorDateTime: header.OriginatorDateTime,
|
||||
Category: header.Category,
|
||||
Body: header.Body,
|
||||
Content: header.Content,
|
||||
BodyData: bodyData,
|
||||
ReceivedAt: header.ReceivedAt,
|
||||
ParsedAt: header.ParsedAt,
|
||||
Parsed: true,
|
||||
Status: dto.MessageStatusParsed,
|
||||
ErrorReason: "",
|
||||
}
|
||||
|
||||
parsed.Uuid = uuid.New().String()
|
||||
|
||||
return parsed, nil
|
||||
}
|
||||
|
||||
func cleanMessage(text string) string {
|
||||
cleanedText := emptyLineRemove.ReplaceAllString(text, "")
|
||||
cleanText := strings.ReplaceAll(cleanedText, "\n\n", "\n")
|
||||
if match := bodyOnly.FindStringSubmatch(cleanText); len(match) > 1 {
|
||||
bodyContent := match[2]
|
||||
if bodyContent[len(bodyContent)-1] == '\n' {
|
||||
return bodyContent[:len(bodyContent)-1]
|
||||
}
|
||||
return bodyContent
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// ParseHeader parses only the header portion of the message and returns a lightweight struct
|
||||
// with header fields and body content. It is used internally by the aviation parser.
|
||||
type Header struct {
|
||||
MessageID string
|
||||
DateTime string
|
||||
PriorityIndicator string
|
||||
PrimaryAddress string
|
||||
SecondaryAddresses string
|
||||
Originator string
|
||||
OriginatorDateTime string
|
||||
Category string
|
||||
Content string
|
||||
Body string
|
||||
ReceivedAt time.Time
|
||||
ParsedAt time.Time
|
||||
}
|
||||
|
||||
func ParseHeader(fullMessage string) (Header, error) {
|
||||
log := zap.S()
|
||||
cleaned := cleanMessage(fullMessage)
|
||||
lines := strings.Split(cleaned, "\n")
|
||||
|
||||
if len(lines) < 3 {
|
||||
log.Warnf("invalid message format: %s", fullMessage)
|
||||
return Header{Content: fullMessage}, fmt.Errorf("invalid message format: %s", fullMessage)
|
||||
}
|
||||
|
||||
_, messageID, dateTime, err := parseStartIndicator(lines[0])
|
||||
if err != nil {
|
||||
return Header{Content: fullMessage}, err
|
||||
}
|
||||
|
||||
priorityIndicator, primaryAddress := parsePriorityAndPrimary(lines[1])
|
||||
secondaryAddresses, originator, originatorDateTime, body := parseRemainingLines(lines[2:])
|
||||
|
||||
return Header{
|
||||
MessageID: messageID,
|
||||
DateTime: dateTime,
|
||||
PriorityIndicator: priorityIndicator,
|
||||
PrimaryAddress: primaryAddress,
|
||||
SecondaryAddresses: secondaryAddresses,
|
||||
Originator: originator,
|
||||
OriginatorDateTime: originatorDateTime,
|
||||
Content: fullMessage,
|
||||
Body: body,
|
||||
ReceivedAt: time.Now(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func parseStartIndicator(line string) (string, string, string, error) {
|
||||
parts := strings.Fields(line)
|
||||
if len(parts) >= 3 && strings.HasPrefix(parts[0], StartIndicatorPrefix) {
|
||||
return parts[0], parts[1], parts[2], nil
|
||||
}
|
||||
zap.S().Warnf("invalid start indicator line format: %s", line)
|
||||
return "", "", "", fmt.Errorf("invalid start indicator line format: %s", line)
|
||||
}
|
||||
|
||||
func parsePriorityAndPrimary(line string) (string, string) {
|
||||
parts := strings.Fields(line)
|
||||
if len(parts) >= 2 {
|
||||
return parts[0], parts[1]
|
||||
}
|
||||
zap.S().Warnf("invalid priority and primary address line format: %s", line)
|
||||
return "", ""
|
||||
}
|
||||
|
||||
func parseRemainingLines(lines []string) (string, string, string, string) {
|
||||
var (
|
||||
secondaryAddresses string
|
||||
originator string
|
||||
originatorDateTime string
|
||||
bodyAndFooter strings.Builder
|
||||
headerEnded bool
|
||||
)
|
||||
|
||||
for _, line := range lines {
|
||||
line = strings.TrimSpace(line)
|
||||
if headerEnded {
|
||||
bodyAndFooter.WriteString(line + "\n")
|
||||
} else {
|
||||
switch {
|
||||
case line == EndHeaderMarker:
|
||||
case strings.HasPrefix(line, "."):
|
||||
originatorInfo := strings.Fields(line[1:])
|
||||
if len(originatorInfo) >= 2 {
|
||||
originator = originatorInfo[0]
|
||||
originatorDateTime = originatorInfo[1]
|
||||
}
|
||||
headerEnded = true
|
||||
case strings.HasPrefix(line, BeginPartMarker) || strings.HasPrefix(line, "("):
|
||||
headerEnded = true
|
||||
if strings.Index(line, "NNNN") > 0 {
|
||||
break
|
||||
}
|
||||
bodyAndFooter.WriteString(line + "\n")
|
||||
default:
|
||||
if o1, o2 := getOriginator(line); o1 != "" {
|
||||
originatorDateTime = o1
|
||||
originator = o2
|
||||
} else {
|
||||
secondaryAddresses = secondaryAddresses + " " + line
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return secondaryAddresses, originator, originatorDateTime, bodyAndFooter.String()
|
||||
}
|
||||
|
||||
func getOriginator(line string) (string, string) {
|
||||
match := originator.FindStringSubmatch(line)
|
||||
if len(match) >= 3 {
|
||||
return match[1], match[2]
|
||||
}
|
||||
zap.S().Warnf("invalid originator line format: %s", line)
|
||||
return "", ""
|
||||
}
|
||||
|
||||
func parseOther(text string) map[string]string {
|
||||
data := make(map[string]string)
|
||||
for _, re := range otherPatterns {
|
||||
if match := re.FindStringSubmatch(text); len(match) > 0 {
|
||||
for i, name := range re.SubexpNames() {
|
||||
if i != 0 && name != "" {
|
||||
data[name] = strings.TrimSpace(match[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return data
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"caatsm/internal/model"
|
||||
"caatsm/internal/parsers"
|
||||
)
|
||||
|
||||
// AviationParser implements the Parser interface using the existing parsers package
|
||||
type AviationParser struct{}
|
||||
|
||||
// NewAviationParser creates a new aviation parser
|
||||
func NewAviationParser() *AviationParser {
|
||||
return &AviationParser{}
|
||||
}
|
||||
|
||||
// Parse parses a raw message string and returns a ParsedTelegram
|
||||
func (p *AviationParser) Parse(rawText string) (*model.ParsedTelegram, error) {
|
||||
// Use the existing Parse function from internal/parsers
|
||||
return parsers.Parse(rawText)
|
||||
}
|
||||
@@ -0,0 +1,420 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"caatsm/internal/domain"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
var _ = Describe("Aviation Parser", func() {
|
||||
Describe("ParseHeader", func() {
|
||||
|
||||
Context("with a real ARR context", func() {
|
||||
message := `ZCZC TMQ2530 141614
|
||||
GG ZBTJZXZX
|
||||
141614 ZSHCZTZX
|
||||
(ARR-CES5470-ZBTJ-ZSHC1614)
|
||||
NNNN`
|
||||
It("should get a clean body text", func() {
|
||||
body := cleanMessage(message)
|
||||
expected := `ZCZC TMQ2530 141614
|
||||
GG ZBTJZXZX
|
||||
141614 ZSHCZTZX
|
||||
(ARR-CES5470-ZBTJ-ZSHC1614)`
|
||||
Expect(body).To(Equal(expected))
|
||||
})
|
||||
})
|
||||
|
||||
It("should parse the header correctly", func() {
|
||||
message := `
|
||||
ZCZC TAF6789 160530
|
||||
QU TSNZPCA
|
||||
.
|
||||
QU PEKUDCA TSNUOCA TSNZPCA TSNUFCA
|
||||
.TAF WSSS 160500Z 1606/1712 20010KT 9999 SCT018
|
||||
BECMG 1608/1610 24012KT 9999 SCT018
|
||||
TEMPO 1610/1612 4000 SHRA BKN012
|
||||
BECMG 1612/1614 18008KT 9999 SCT020
|
||||
|
||||
BEGIN PART 02
|
||||
|
||||
(FORECAST AMENDMENT
|
||||
VALID 1606/1700
|
||||
THUNDERSTORMS EXPECTED
|
||||
ALTERNATE ROUTES ADVISED)
|
||||
|
||||
NNNN`
|
||||
parsedHeader, err := ParseHeader(message)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(parsedHeader.MessageID).To(Equal("TAF6789"))
|
||||
Expect(parsedHeader.DateTime).To(Equal("160530"))
|
||||
Expect(parsedHeader.PriorityIndicator).To(Equal("QU"))
|
||||
Expect(parsedHeader.PrimaryAddress).To(Equal("TSNZPCA"))
|
||||
Expect(parsedHeader.SecondaryAddresses).To(Equal(" QU PEKUDCA TSNUOCA TSNZPCA TSNUFCA"))
|
||||
})
|
||||
|
||||
It("should parse the header correctly with originator information", func() {
|
||||
message := `
|
||||
ZCZC NOTAM1122 171000
|
||||
QU TSNZPCA
|
||||
.
|
||||
QU PEKUDCA TSNUOCA TSNZPCA TSNUFCA
|
||||
.SELOZKE 170999
|
||||
|
||||
BEGIN PART 01
|
||||
|
||||
RUNWAY MAINTENANCE NOTICE.
|
||||
|
||||
- MAINTENANCE MANAGER: JOHN DOE
|
||||
|
||||
RUNWAY 09/27 WILL BE CLOSED FOR MAINTENANCE FROM 0800Z TO 1600Z.
|
||||
|
||||
- AIRPORT OPERATIONS: SIGN . . . . . . . . . .
|
||||
|
||||
WE ACKNOWLEDGE THE RUNWAY CLOSURE.
|
||||
|
||||
- CONTROL TOWER:
|
||||
|
||||
SIGN . . . . . . . . . .
|
||||
|
||||
BEGIN PART 02
|
||||
|
||||
(ALERT MESSAGE - WEATHER WARNING
|
||||
VALID 1500Z - 1800Z
|
||||
SEVERE THUNDERSTORM FORECASTED
|
||||
ALL DEPARTURES/ARRIVALS EXPECTED TO BE DELAYED)
|
||||
|
||||
NNNN`
|
||||
parsedHeader, err := ParseHeader(message)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(parsedHeader.MessageID).To(Equal("NOTAM1122"))
|
||||
Expect(parsedHeader.DateTime).To(Equal("171000"))
|
||||
Expect(parsedHeader.PriorityIndicator).To(Equal("QU"))
|
||||
Expect(parsedHeader.PrimaryAddress).To(Equal("TSNZPCA"))
|
||||
Expect(parsedHeader.SecondaryAddresses).To(Equal(" QU PEKUDCA TSNUOCA TSNZPCA TSNUFCA"))
|
||||
Expect(parsedHeader.Originator).To(Equal("SELOZKE"))
|
||||
Expect(parsedHeader.OriginatorDateTime).To(Equal("170999"))
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
Describe("Other Info", func() {
|
||||
Context("PBN/A1B2B3B4B5D1L1 NAV/ABAS REG/B6513 EET/ZBPE0112 SEL/KMAL PER/C RIF/FRT N640 ZBYN RMK/TCAS EQUIPPED", func() {
|
||||
It("should parse the other info correctly", func() {
|
||||
otherInfo := "PBN/A1B2B3B4B5D1L1 NAV/ABAS REG/B6513 EET/ZBPE0112 SEL/KMAL PER/C RIF/FRT N640 ZBYN RMK/TCAS EQUIPPED"
|
||||
parsed := parseOther(otherInfo)
|
||||
Expect(parsed).ToNot(BeNil())
|
||||
Expect(parsed[PBN]).To(Equal("A1B2B3B4B5D1L1"))
|
||||
Expect(parsed[NavigationEquipment]).To(Equal("ABAS"))
|
||||
Expect(parsed[Register]).To(Equal("B6513"))
|
||||
Expect(parsed[EstimatedElapsedTime]).To(Equal("ZBPE0112"))
|
||||
Expect(parsed[SELCALCode]).To(Equal("KMAL"))
|
||||
Expect(parsed[PerformanceCategory]).To(Equal("C"))
|
||||
Expect(parsed[RerouteInformation]).To(Equal("FRT N640 ZBYN"))
|
||||
Expect(parsed[Remarks]).To(Equal("TCAS EQUIPPED"))
|
||||
})
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
Describe("ParseBody", func() {
|
||||
|
||||
Context("with ARR body (ARR-CES5470-ZBTJ-ZSHC1614)", func() {
|
||||
body := "(ARR-CES5470-ZBTJ-ZSHC1614)"
|
||||
parser := NewBodyParser(body)
|
||||
It("should parse the body correctly", func() {
|
||||
category, parsedBody, err := parser.Parse()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(parsedBody).ToNot(BeNil())
|
||||
Expect(category).To(Equal("ARR"))
|
||||
Expect(parsedBody).To(BeAssignableToTypeOf(&domain.ARR{}))
|
||||
arrMessage := parsedBody.(*domain.ARR)
|
||||
Expect(arrMessage.Category).To(Equal("ARR"))
|
||||
Expect(arrMessage.AircraftID).To(Equal("CES5470"))
|
||||
Expect(arrMessage.DepartureAirport).To(Equal("ZBTJ"))
|
||||
Expect(arrMessage.ArrivalAirport).To(Equal("ZSHC"))
|
||||
Expect(arrMessage.ArrivalTime).To(Equal("1614"))
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
Context("with ARR body", func() {
|
||||
// parser := NewBodyParser(body)
|
||||
It("should parse the body (ARR-AB123/A1234-KJFK-KLAX1234) correctly", func() {
|
||||
body := " (ARR-AB123/A1234-KJFK-KLAX1234)"
|
||||
parser := NewBodyParser(body)
|
||||
category, parsedBody, err := parser.Parse()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(parsedBody).ToNot(BeNil())
|
||||
Expect(category).To(Equal("ARR"))
|
||||
Expect(parsedBody).To(BeAssignableToTypeOf(&domain.ARR{}))
|
||||
arrMessage := parsedBody.(*domain.ARR)
|
||||
Expect(arrMessage.Category).To(Equal("ARR"))
|
||||
Expect(arrMessage.AircraftID).To(Equal("AB123"))
|
||||
Expect(arrMessage.SSRModeAndCode).To(Equal("A1234"))
|
||||
Expect(arrMessage.DepartureAirport).To(Equal("KJFK"))
|
||||
Expect(arrMessage.ArrivalAirport).To(Equal("KLAX"))
|
||||
})
|
||||
|
||||
It("should parse the body (ARR-JAE7433/A0132-RKSI-ZBTJ1604) correctly", func() {
|
||||
body := " (ARR-JAE7433/A0132-RKSI-ZBTJ1604)"
|
||||
parser := NewBodyParser(body)
|
||||
category, parsedBody, err := parser.Parse()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(parsedBody).ToNot(BeNil())
|
||||
Expect(category).To(Equal("ARR"))
|
||||
Expect(parsedBody).To(BeAssignableToTypeOf(&domain.ARR{}))
|
||||
arrMessage := parsedBody.(*domain.ARR)
|
||||
Expect(arrMessage.Category).To(Equal("ARR"))
|
||||
Expect(arrMessage.AircraftID).To(Equal("JAE7433"))
|
||||
Expect(arrMessage.SSRModeAndCode).To(Equal("A0132"))
|
||||
Expect(arrMessage.DepartureAirport).To(Equal("RKSI"))
|
||||
Expect(arrMessage.ArrivalAirport).To(Equal("ZBTJ"))
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
Context("with DEP body", func() {
|
||||
// parser := NewBodyParser()
|
||||
It("should parse the body (DEP-CYZ9017/A5633-ZBTJ1638-ZSPD) correctly", func() {
|
||||
body := "(DEP-CYZ9017/A5633-ZBTJ1638-ZSPD)"
|
||||
parser := NewBodyParser(body)
|
||||
category, parsedBody, err := parser.Parse()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(parsedBody).ToNot(BeNil())
|
||||
Expect(category).To(Equal("DEP"))
|
||||
Expect(parsedBody).To(BeAssignableToTypeOf(&domain.DEP{}))
|
||||
depMessage := parsedBody.(*domain.DEP)
|
||||
Expect(depMessage.Category).To(Equal("DEP"))
|
||||
Expect(depMessage.AircraftID).To(Equal("CYZ9017"))
|
||||
Expect(depMessage.SSRModeAndCode).To(Equal("A5633"))
|
||||
Expect(depMessage.DepartureAirport).To(Equal("ZBTJ"))
|
||||
Expect(depMessage.DepartureTime).To(Equal("1638"))
|
||||
Expect(depMessage.Destination).To(Equal("ZSPD"))
|
||||
})
|
||||
})
|
||||
|
||||
Context("with FPL body", func() {
|
||||
// parser := NewBodyParser()
|
||||
It("should parse the body correctly", func() {
|
||||
body := `(FPL-CCA1532-IS
|
||||
-A332/H
|
||||
-SDE3FGHIJ4J5M1RWY/LB101
|
||||
-ZSSS2035
|
||||
-K0859S1040 PIAKS G330 PIMOL A539 BTO W82 DOGAR
|
||||
-ZBAA0153 ZBYN
|
||||
-PBN/A1B2B3B4B5D1L1 NAV/ABAS REG/B6513 EET/ZBPE0112 SEL/KMAL PER/C RIF/FRT N640 ZBYN RMK/TCAS EQUIPPED)`
|
||||
parser := NewBodyParser(body)
|
||||
category, parsedBody, err := parser.Parse()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(parsedBody).ToNot(BeNil())
|
||||
Expect(category).To(Equal("FPL"))
|
||||
Expect(parsedBody).To(BeAssignableToTypeOf(&domain.FPL{}))
|
||||
fplMessage := parsedBody.(*domain.FPL)
|
||||
Expect(fplMessage.FlightNumber).To(Equal("CCA1532"))
|
||||
Expect(fplMessage.FlightRulesAndType).To(Equal("IS"))
|
||||
Expect(fplMessage.AircraftID).To(Equal("A332/H"))
|
||||
Expect(fplMessage.SSRModeAndCode).To(Equal("SDE3FGHIJ4J5M1RWY/LB101"))
|
||||
Expect(fplMessage.DepartureAirport).To(Equal("ZSSS"))
|
||||
Expect(fplMessage.DepartureTime).To(Equal("2035"))
|
||||
Expect(fplMessage.CruisingSpeedAndLevel).To(Equal("K0859S1040"))
|
||||
Expect(fplMessage.Route).To(Equal("PIAKS G330 PIMOL A539 BTO W82 DOGAR"))
|
||||
Expect(fplMessage.DestinationAndTotalTime).To(Equal("ZBAA0153"))
|
||||
Expect(fplMessage.AlternateAirport).To(Equal("ZBYN"))
|
||||
Expect(fplMessage.OtherInfo).To(Equal("PBN/A1B2B3B4B5D1L1 NAV/ABAS REG/B6513 EET/ZBPE0112 SEL/KMAL PER/C RIF/FRT N640 ZBYN RMK/TCAS EQUIPPED"))
|
||||
Expect(fplMessage.PBN).To(Equal("A1B2B3B4B5D1L1"))
|
||||
Expect(fplMessage.EstimatedElapsedTime).To(Equal("ZBPE0112"))
|
||||
Expect(fplMessage.SELCALCode).To(Equal("KMAL"))
|
||||
Expect(fplMessage.PerformanceCategory).To(Equal("C"))
|
||||
Expect(fplMessage.RerouteInformation).To(Equal("FRT N640 ZBYN"))
|
||||
Expect(fplMessage.Remarks).To(Equal("TCAS EQUIPPED"))
|
||||
})
|
||||
})
|
||||
|
||||
Context("with CNL body", func() {
|
||||
// parser := NewBodyParser()
|
||||
It("should parse the body correctly", func() {
|
||||
body := "(CNL-YZR7979-ZSPD-ZBTJ)"
|
||||
parser := NewBodyParser(body)
|
||||
category, parsedBody, err := parser.Parse()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(parsedBody).ToNot(BeNil())
|
||||
Expect(category).To(Equal("CNL"))
|
||||
Expect(parsedBody).To(BeAssignableToTypeOf(&domain.CNL{}))
|
||||
cnlMessage := parsedBody.(*domain.CNL)
|
||||
Expect(cnlMessage.AircraftID).To(Equal("YZR7979"))
|
||||
})
|
||||
})
|
||||
|
||||
Context("with DLA body", func() {
|
||||
It("should parse the body correctly", func() {
|
||||
body := "(DLA-CSN3133-ZGGG0110-ZBTJ)"
|
||||
parser := NewBodyParser(body)
|
||||
category, parsedBody, err := parser.Parse()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(parsedBody).ToNot(BeNil())
|
||||
Expect(category).To(Equal("DLA"))
|
||||
Expect(parsedBody).To(BeAssignableToTypeOf(&domain.DLA{}))
|
||||
dlaMessage := parsedBody.(*domain.DLA)
|
||||
Expect(dlaMessage.AircraftID).To(Equal("CSN3133"))
|
||||
Expect(dlaMessage.DepartureAirport).To(Equal("ZGGG"))
|
||||
Expect(dlaMessage.NewDepartureTime).To(Equal("0110"))
|
||||
Expect(dlaMessage.ArrivalAirport).To(Equal("ZBTJ"))
|
||||
|
||||
})
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
Describe("Parse whole real message", func() {
|
||||
|
||||
Context("with a real ARR message", func() {
|
||||
message := `
|
||||
ZCZC TMQ2526 141605
|
||||
|
||||
FF ZBTJZPZX
|
||||
|
||||
141604 ZBACZQZX
|
||||
|
||||
(ARR-JAE7433/A0132-RKSI-ZBTJ1604)
|
||||
|
||||
NNNN
|
||||
`
|
||||
It("should parse the whole message correctly", func() {
|
||||
parsedMessage, err := Parse(message)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(parsedMessage).ToNot(BeNil())
|
||||
Expect(parsedMessage.Parsed).To(BeTrue())
|
||||
Expect(parsedMessage.MessageID).To(Equal("TMQ2526"))
|
||||
Expect(parsedMessage.DateTime).To(Equal("141605"))
|
||||
Expect(parsedMessage.PrimaryAddress).To(Equal("ZBTJZPZX"))
|
||||
Expect(parsedMessage.SecondaryAddresses).To(Equal(""))
|
||||
Expect(parsedMessage.PriorityIndicator).To(Equal("FF"))
|
||||
Expect(parsedMessage.OriginatorDateTime).To(Equal("141604"))
|
||||
Expect(parsedMessage.Originator).To(Equal("ZBACZQZX"))
|
||||
|
||||
arrmsg := parsedMessage.BodyData.(*domain.ARR)
|
||||
Expect(arrmsg.Category).To(Equal("ARR"))
|
||||
Expect(arrmsg.AircraftID).To(Equal("JAE7433"))
|
||||
Expect(arrmsg.SSRModeAndCode).To(Equal("A0132"))
|
||||
Expect(arrmsg.DepartureAirport).To(Equal("RKSI"))
|
||||
Expect(arrmsg.ArrivalAirport).To(Equal("ZBTJ"))
|
||||
Expect(arrmsg.ArrivalTime).To(Equal("1604"))
|
||||
})
|
||||
})
|
||||
|
||||
Context("with this real FPL message", func() {
|
||||
message := `ZCZC TMQ2617 142150
|
||||
|
||||
|
||||
GG ZBTJZPZX
|
||||
|
||||
|
||||
150551 ZBTJUOBK
|
||||
|
||||
|
||||
(FPL-OKA2861-IS
|
||||
|
||||
|
||||
-MA60/M-SHID/C
|
||||
|
||||
|
||||
-ZBTJ0030
|
||||
|
||||
|
||||
-K0420S0450 CG J1 FZ
|
||||
|
||||
|
||||
-ZSYT0100 ZSQD ZYTL
|
||||
|
||||
|
||||
-REG/B3710 SEL/ RMK/TCAS )
|
||||
|
||||
NNNN
|
||||
`
|
||||
It("should parse the whole message correctly", func() {
|
||||
parsedMessage, err := Parse(message)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(parsedMessage).ToNot(BeNil())
|
||||
Expect(parsedMessage.Parsed).To(BeTrue())
|
||||
Expect(parsedMessage.MessageID).To(Equal("TMQ2617"))
|
||||
Expect(parsedMessage.DateTime).To(Equal("142150"))
|
||||
Expect(parsedMessage.PrimaryAddress).To(Equal("ZBTJZPZX"))
|
||||
Expect(parsedMessage.SecondaryAddresses).To(Equal(""))
|
||||
Expect(parsedMessage.PriorityIndicator).To(Equal("GG"))
|
||||
Expect(parsedMessage.OriginatorDateTime).To(Equal("150551"))
|
||||
Expect(parsedMessage.Originator).To(Equal("ZBTJUOBK"))
|
||||
|
||||
fplmsg := parsedMessage.BodyData.(*domain.FPL)
|
||||
Expect(fplmsg.Category).To(Equal("FPL"))
|
||||
Expect(fplmsg.FlightNumber).To(Equal("OKA2861"))
|
||||
Expect(fplmsg.FlightRulesAndType).To(Equal("IS"))
|
||||
Expect(fplmsg.AircraftID).To(Equal("MA60/M"))
|
||||
Expect(fplmsg.SSRModeAndCode).To(Equal("SHID/C"))
|
||||
Expect(fplmsg.DepartureAirport).To(Equal("ZBTJ"))
|
||||
Expect(fplmsg.DepartureTime).To(Equal("0030"))
|
||||
Expect(fplmsg.CruisingSpeedAndLevel).To(Equal("K0420S0450"))
|
||||
Expect(fplmsg.Route).To(Equal("CG J1 FZ"))
|
||||
Expect(fplmsg.DestinationAndTotalTime).To(Equal("ZSYT0100"))
|
||||
Expect(fplmsg.AlternateAirport).To(Equal("ZSQD ZYTL"))
|
||||
Expect(fplmsg.OtherInfo).To(Equal("REG/B3710 SEL/ RMK/TCAS"))
|
||||
// Expect(fplmsg.PBN).To(Equal("B3710"))
|
||||
Expect(fplmsg.SELCALCode).To(Equal(""))
|
||||
Expect(fplmsg.Remarks).To(Equal("TCAS"))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Describe("Utility Functions", func() {
|
||||
|
||||
It("should clean text correctly", func() {
|
||||
text := `ZCZC TMQ2530 141614
|
||||
|
||||
1234
|
||||
4567
|
||||
NNNN`
|
||||
expect := "ZCZC TMQ2530 141614\n1234\n 4567"
|
||||
cleaned := cleanMessage(text)
|
||||
Expect(cleaned).To(Equal(expect))
|
||||
})
|
||||
|
||||
It("should parse start indicator correctly", func() {
|
||||
line := "ZCZC TMQ2530 141614"
|
||||
startIndicator, messageID, dateTime, err := parseStartIndicator(line)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(startIndicator).To(Equal("ZCZC"))
|
||||
Expect(messageID).To(Equal("TMQ2530"))
|
||||
Expect(dateTime).To(Equal("141614"))
|
||||
})
|
||||
|
||||
It("should return error for invalid start indicator line", func() {
|
||||
line := "Invalid Line"
|
||||
_, _, _, err := parseStartIndicator(line)
|
||||
Expect(err).To(HaveOccurred())
|
||||
})
|
||||
|
||||
It("should parse priority and primary address correctly", func() {
|
||||
line := "QU TSNZPCA"
|
||||
priority, primary := parsePriorityAndPrimary(line)
|
||||
Expect(priority).To(Equal("QU"))
|
||||
Expect(primary).To(Equal("TSNZPCA"))
|
||||
})
|
||||
|
||||
It("should return empty strings for invalid priority and primary address line", func() {
|
||||
line := "Invalid-Line"
|
||||
priority, primary := parsePriorityAndPrimary(line)
|
||||
Expect(priority).To(BeEmpty())
|
||||
Expect(primary).To(BeEmpty())
|
||||
})
|
||||
|
||||
It("should parse remaining lines correctly", func() {
|
||||
lines := []string{"QU PEKUDCA TSNUOCA TSNZPCA TSNUFCA", ".SELOZKE 170999", "BEGIN PART 01"}
|
||||
secondaryAddresses, originator, originatorDateTime, bodyAndFooter := parseRemainingLines(lines)
|
||||
Expect(secondaryAddresses).To(Equal(" QU PEKUDCA TSNUOCA TSNZPCA TSNUFCA"))
|
||||
Expect(originator).To(Equal("SELOZKE"))
|
||||
Expect(originatorDateTime).To(Equal("170999"))
|
||||
Expect(bodyAndFooter).To(Equal("BEGIN PART 01\n"))
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,72 @@
|
||||
package parser
|
||||
|
||||
import "regexp"
|
||||
|
||||
// String constants
|
||||
const (
|
||||
StartIndicatorPrefix = "ZCZC"
|
||||
EndHeaderMarker = "."
|
||||
BeginPartMarker = "BEGIN PART"
|
||||
|
||||
Category = "category"
|
||||
CategoryArrival = "ARR"
|
||||
CategoryDeparture = "DEP"
|
||||
CategoryCancellation = "CNL"
|
||||
CategoryDelay = "DLA"
|
||||
CategoryFlightPlan = "FPL"
|
||||
|
||||
CANCELLED = "CNL"
|
||||
AirportCode = "airport"
|
||||
Date = "date"
|
||||
Task = "task"
|
||||
Index = "idx"
|
||||
FlightNumber = "number"
|
||||
Register = "reg"
|
||||
)
|
||||
|
||||
// Regular expression patterns
|
||||
const (
|
||||
AllDigitsPattern = `^(?P<dep_time>\d+)$`
|
||||
IndexPattern = `^(?P<idx>\(?L?[0-9]+\)?:?\.?)$`
|
||||
DatePattern = `^(?P<date>\d{2}\w{3})$`
|
||||
TaskPattern = `(?P<task>[A-Z]\/[A-Z])$`
|
||||
WaypointPattern = `^(SI:)?(?P<arr_time>\d{4}(\(\d{2}[A-Z]{3}\))?)?\/?(?P<airport>[A-Z]{3})\/?(?P<dep_time>\d{4}(\(\d{2}[A-Z]{3}\))?)?$`
|
||||
FlightNumberPattern = `^(?P<number>[0-9A-Z][0-9A-Z]\d{3,5}(\/\d+)*)$`
|
||||
RegisterPattern = `^(?P<reg>B\d{4})$`
|
||||
|
||||
ArrPatternString = `^\((?P<category>[A-Z]{3})-(?P<number>[A-Z0-9]+)(\/?(?P<ssr>[A-Z0-9]+))?-(?P<dep>[A-Z]{4})-(?P<arr>[A-Z]{4})(?P<arr_time>\d{4})\)$`
|
||||
DepPatternString = `^\((?P<category>[A-Z]{3})-(?P<number>[A-Z0-9]+)(\/(?P<ssr>[A-Z0-9]+))?-(?P<dep>[A-Z]{4})(?P<dep_time>\d{4})-(?P<arr>[A-Z]{4})\)$`
|
||||
FplPatternString = `\((?P<category>[A-Z]{3})-(?P<number>[A-Z]+\d+)-(?P<indicator>[A-Z]{2})\n-(?P<aircraft>[A-Z]+\d+\/?[A-Z]?)\n?-(?P<surve>.*)\n?-(?P<dep>[A-Z]{4})(?P<dep_time>\d{4})\n?-(?P<speed>[A-Z]+\d+)(?P<level>[A-Z0-9]+)\s+(?P<route>(.|\n)+)\n-(?P<dest>[A-Z]{4})(?P<estt>\d{4})\s?(?P<alter>(\s[A-Z]{4})+)\n?-([A-Z]{3}\/(?:[A-Z]{4}\d{4}\s?)+)?(?P<other>(?m)[A-Z]{3}\/(.|\n)*)\)$`
|
||||
CnlPatternString = `^\((?P<category>[A-Z]{3})-(?P<number>\w+\d+)-?(?P<dep>[A-Z]{4})?-?(?<arr>[A-Z]{4})\)$`
|
||||
DlaPatternString = `^\((?P<category>[A-Z]{3})-(?P<number>\w+\d+)-?(?P<dep>[A-Z]{4})(?P<dep_time>\d{4})?-?(?<arr>[A-Z]{4})(?<arr_time>\d{4})?\)$`
|
||||
)
|
||||
|
||||
// Compiled regular expressions
|
||||
var (
|
||||
AllDigitsExpression = regexp.MustCompile(AllDigitsPattern)
|
||||
IndexExpression = regexp.MustCompile(IndexPattern)
|
||||
TaskExpression = regexp.MustCompile(TaskPattern)
|
||||
DateExpression = regexp.MustCompile(DatePattern)
|
||||
WaypointExpression = regexp.MustCompile(WaypointPattern)
|
||||
FlightNumberExpression = regexp.MustCompile(FlightNumberPattern)
|
||||
RegisterExpression = regexp.MustCompile(RegisterPattern)
|
||||
ArrPatternExpression = regexp.MustCompile(ArrPatternString)
|
||||
DepPatternExpression = regexp.MustCompile(DepPatternString)
|
||||
FplPatternExpression = regexp.MustCompile(FplPatternString)
|
||||
CnlPatternExpression = regexp.MustCompile(CnlPatternString)
|
||||
DlaPatternExpression = regexp.MustCompile(DlaPatternString)
|
||||
BodyTypePattern = regexp.MustCompile(`^\(([A-Z]{3})(.*\n?)+\)$`)
|
||||
|
||||
categoryRegex = regexp.MustCompile(`\((?P<category>[A-Z]+)-`)
|
||||
emptyLineRemove = regexp.MustCompile(`(?m)^\s*$`)
|
||||
bodyOnly = regexp.MustCompile(`(.|\n)?(ZCZC(.|\n)*)NNNN(.|\n)?$`)
|
||||
originator = regexp.MustCompile(`(?P<originatorDateTime>[0-9]+)\s(?P<originator>[A-Z]+)`)
|
||||
navPattern = regexp.MustCompile(`(?m)NAV\/(?P<nav>\w+)`)
|
||||
remarkPattern = regexp.MustCompile(`(?s)RMK\/(?P<remark>.*)`)
|
||||
selPattern = regexp.MustCompile(`(?m)SEL\/(?P<sel>\w+)`)
|
||||
regPattern = regexp.MustCompile(`(?m)REG\/(?P<reg>[A-Z0-9]+)`)
|
||||
pbnPattern = regexp.MustCompile(`(?m)PBN\/(?P<pbn>[A-Z0-9]+)`)
|
||||
eetPattern = regexp.MustCompile(`(?s)(-?EET\/(?P<eet>(?:[A-Z]{4}\d{4}\s*)+))`)
|
||||
performancePattern = regexp.MustCompile(`(?s)-?PER\/(?P<per>\w)`)
|
||||
reroutePattern = regexp.MustCompile(`(?m)RIF\/(?P<rif>.*)[A-Z]{3}\/`)
|
||||
)
|
||||
@@ -1,9 +1,9 @@
|
||||
package parser
|
||||
|
||||
import "caatsm/internal/model"
|
||||
import "caatsm/internal/adapter/dto"
|
||||
|
||||
// Parser defines the interface for parsing raw telegram messages
|
||||
type Parser interface {
|
||||
// Parse parses a raw message string and returns a ParsedTelegram
|
||||
Parse(rawText string) (*model.ParsedTelegram, error)
|
||||
Parse(rawText string) (*dto.ParsedTelegram, error)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,327 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
)
|
||||
|
||||
// BodyConfig represents the configuration for parsing message bodies.
|
||||
type BodyConfig struct {
|
||||
Patterns []PatternConfig
|
||||
}
|
||||
|
||||
// PatternConfig represents the configuration for a specific pattern.
|
||||
type PatternConfig struct {
|
||||
Pattern string
|
||||
Comments string
|
||||
Expression *regexp.Regexp
|
||||
}
|
||||
|
||||
// LineParser represents a line parser configuration.
|
||||
type LineParser struct {
|
||||
Airlines []string
|
||||
MinLen int
|
||||
WaypointStart int
|
||||
Fields map[int]string
|
||||
}
|
||||
|
||||
var (
|
||||
bodyPatterns = map[string]BodyConfig{}
|
||||
parserMap = map[string]*regexp.Regexp{}
|
||||
parserDef = &[]LineParser{}
|
||||
)
|
||||
|
||||
func init() {
|
||||
// Initialize body patterns.
|
||||
bodyPatterns = map[string]BodyConfig{
|
||||
"ARR": {
|
||||
Patterns: []PatternConfig{
|
||||
{
|
||||
Pattern: ArrPatternString,
|
||||
Comments: "Pattern for ARR message",
|
||||
Expression: ArrPatternExpression,
|
||||
},
|
||||
},
|
||||
},
|
||||
"DEP": {
|
||||
Patterns: []PatternConfig{
|
||||
{
|
||||
Pattern: DepPatternString,
|
||||
Comments: "Pattern for DEP message",
|
||||
Expression: DepPatternExpression,
|
||||
},
|
||||
},
|
||||
},
|
||||
"FPL": {
|
||||
Patterns: []PatternConfig{
|
||||
{
|
||||
Pattern: FplPatternString,
|
||||
Comments: "Pattern for FPL message",
|
||||
Expression: FplPatternExpression,
|
||||
},
|
||||
},
|
||||
},
|
||||
"CNL": {
|
||||
Patterns: []PatternConfig{
|
||||
{
|
||||
Pattern: CnlPatternString,
|
||||
Comments: "Pattern for CNL message",
|
||||
Expression: CnlPatternExpression,
|
||||
},
|
||||
},
|
||||
},
|
||||
"DLA": {
|
||||
Patterns: []PatternConfig{
|
||||
{
|
||||
Pattern: DlaPatternString,
|
||||
Comments: "Pattern for DLA message",
|
||||
Expression: DlaPatternExpression,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Initialize parser map.
|
||||
parserMap = map[string]*regexp.Regexp{
|
||||
Index: IndexExpression,
|
||||
Task: TaskExpression,
|
||||
Date: DateExpression,
|
||||
FlightNumber: FlightNumberExpression,
|
||||
Register: RegisterExpression,
|
||||
}
|
||||
|
||||
// Initialize parser definitions.
|
||||
parserDef = &[]LineParser{
|
||||
{
|
||||
Airlines: []string{"FM"},
|
||||
MinLen: 6,
|
||||
WaypointStart: 5,
|
||||
Fields: map[int]string{
|
||||
0: Task,
|
||||
1: FlightNumber,
|
||||
2: Register,
|
||||
},
|
||||
},
|
||||
{
|
||||
Airlines: []string{"MF"},
|
||||
MinLen: 5,
|
||||
WaypointStart: 4,
|
||||
Fields: map[int]string{
|
||||
0: Index,
|
||||
1: FlightNumber,
|
||||
2: Register,
|
||||
},
|
||||
},
|
||||
{
|
||||
Airlines: []string{"8X"},
|
||||
MinLen: 9,
|
||||
WaypointStart: 7,
|
||||
Fields: map[int]string{
|
||||
0: Index,
|
||||
1: Date,
|
||||
2: FlightNumber,
|
||||
3: Register,
|
||||
},
|
||||
},
|
||||
{
|
||||
Airlines: []string{"HU"},
|
||||
MinLen: 6,
|
||||
WaypointStart: 5,
|
||||
Fields: map[int]string{
|
||||
0: Index,
|
||||
1: Task,
|
||||
2: FlightNumber,
|
||||
3: Register,
|
||||
},
|
||||
},
|
||||
{
|
||||
Airlines: []string{"JD"},
|
||||
MinLen: 7,
|
||||
WaypointStart: 5,
|
||||
Fields: map[int]string{
|
||||
0: Index,
|
||||
1: FlightNumber,
|
||||
2: Register,
|
||||
},
|
||||
},
|
||||
{
|
||||
Airlines: []string{"GS"},
|
||||
MinLen: 4,
|
||||
WaypointStart: 3,
|
||||
Fields: map[int]string{
|
||||
0: Index,
|
||||
1: FlightNumber,
|
||||
2: Register,
|
||||
},
|
||||
},
|
||||
{
|
||||
Airlines: []string{"Y8"},
|
||||
MinLen: 6,
|
||||
WaypointStart: 3,
|
||||
Fields: map[int]string{
|
||||
0: Index,
|
||||
1: FlightNumber,
|
||||
2: Register,
|
||||
},
|
||||
},
|
||||
{
|
||||
Airlines: []string{"3U"},
|
||||
MinLen: 8,
|
||||
WaypointStart: 6,
|
||||
Fields: map[int]string{
|
||||
0: Index,
|
||||
1: Date,
|
||||
2: FlightNumber,
|
||||
3: Register,
|
||||
},
|
||||
},
|
||||
{
|
||||
Airlines: []string{"CK"},
|
||||
MinLen: 4,
|
||||
WaypointStart: 3,
|
||||
Fields: map[int]string{
|
||||
0: Task,
|
||||
1: FlightNumber,
|
||||
2: Register,
|
||||
},
|
||||
},
|
||||
{
|
||||
Airlines: []string{"G5"},
|
||||
MinLen: 8,
|
||||
WaypointStart: 5,
|
||||
Fields: map[int]string{
|
||||
0: Index,
|
||||
1: Task,
|
||||
2: FlightNumber,
|
||||
3: Register,
|
||||
},
|
||||
},
|
||||
{
|
||||
Airlines: []string{"9C"},
|
||||
MinLen: 8,
|
||||
WaypointStart: 6,
|
||||
Fields: map[int]string{
|
||||
0: Date,
|
||||
1: Task,
|
||||
2: FlightNumber,
|
||||
3: Register,
|
||||
},
|
||||
},
|
||||
{
|
||||
Airlines: []string{"ZH"},
|
||||
MinLen: 9,
|
||||
WaypointStart: 7,
|
||||
Fields: map[int]string{
|
||||
0: Index,
|
||||
1: Task,
|
||||
2: Date,
|
||||
3: FlightNumber,
|
||||
4: Register,
|
||||
},
|
||||
},
|
||||
{
|
||||
Airlines: []string{"8L"},
|
||||
MinLen: 6,
|
||||
WaypointStart: 4,
|
||||
Fields: map[int]string{
|
||||
0: Index,
|
||||
1: Task,
|
||||
2: FlightNumber,
|
||||
3: Register,
|
||||
},
|
||||
},
|
||||
{
|
||||
Airlines: []string{"SC"},
|
||||
MinLen: 9,
|
||||
WaypointStart: 7,
|
||||
Fields: map[int]string{
|
||||
0: Index,
|
||||
1: FlightNumber,
|
||||
2: Register,
|
||||
},
|
||||
},
|
||||
{
|
||||
Airlines: []string{"PN"},
|
||||
MinLen: 7,
|
||||
WaypointStart: 5,
|
||||
Fields: map[int]string{
|
||||
0: Index,
|
||||
1: FlightNumber,
|
||||
2: Register,
|
||||
},
|
||||
},
|
||||
{
|
||||
Airlines: []string{"CZ"},
|
||||
MinLen: 6,
|
||||
WaypointStart: 4,
|
||||
Fields: map[int]string{
|
||||
0: Index,
|
||||
1: FlightNumber,
|
||||
2: Register,
|
||||
},
|
||||
},
|
||||
{
|
||||
Airlines: []string{"HO"},
|
||||
MinLen: 7,
|
||||
WaypointStart: 6,
|
||||
Fields: map[int]string{
|
||||
0: Index,
|
||||
1: Date,
|
||||
2: Task,
|
||||
3: FlightNumber,
|
||||
4: Register,
|
||||
},
|
||||
},
|
||||
{
|
||||
Airlines: []string{"NS"},
|
||||
MinLen: 7,
|
||||
WaypointStart: 6,
|
||||
Fields: map[int]string{
|
||||
0: Index,
|
||||
1: Date,
|
||||
2: Task,
|
||||
3: FlightNumber,
|
||||
4: Register,
|
||||
},
|
||||
},
|
||||
{
|
||||
Airlines: []string{"EU"},
|
||||
MinLen: 7,
|
||||
WaypointStart: 6,
|
||||
Fields: map[int]string{
|
||||
0: Task,
|
||||
1: Date,
|
||||
2: FlightNumber,
|
||||
3: Register,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// FindPatterns finds the matching body configuration based on the message body.
|
||||
func FindPatterns(messageBody string) *BodyConfig {
|
||||
if match := BodyTypePattern.FindStringSubmatch(messageBody); len(match) > 1 {
|
||||
name := match[1]
|
||||
if bodyConfig, found := bodyPatterns[name]; found {
|
||||
return &bodyConfig
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ParseBody parses the message body and returns the extracted values.
|
||||
func ParseBody(messageBody string) map[string]string {
|
||||
if body := FindPatterns(messageBody); 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,43 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
var _ = Describe("Pattern Parser", func() {
|
||||
|
||||
Describe("FindPatterns", func() {
|
||||
It("should return the correct BodyConfig based on the message body", func() {
|
||||
message := "(ARR-AB123-SSR1234-KJFK-KLAX)"
|
||||
bodyConfig := FindPatterns(message)
|
||||
Expect(bodyConfig).NotTo(BeNil())
|
||||
// Expect(bodyConfig.Name).To(Equal("ARR"))
|
||||
})
|
||||
|
||||
It("should return nil if no pattern matches", func() {
|
||||
message := "(XYZ-123)"
|
||||
bodyConfig := FindPatterns(message)
|
||||
Expect(bodyConfig).To(BeNil())
|
||||
})
|
||||
})
|
||||
|
||||
Describe("ParseBody", func() {
|
||||
It("should parse the message body and extract data based on patterns", func() {
|
||||
message := "(ARR-AB123/A1234-KJFK-KLAX1234)"
|
||||
parsedData := ParseBody(message)
|
||||
Expect(parsedData).NotTo(BeNil())
|
||||
Expect(parsedData["category"]).To(Equal("ARR"))
|
||||
Expect(parsedData["number"]).To(Equal("AB123"))
|
||||
Expect(parsedData["ssr"]).To(Equal("A1234"))
|
||||
Expect(parsedData[DepartureCode]).To(Equal("KJFK"))
|
||||
Expect(parsedData[ArrivalCode]).To(Equal("KLAX"))
|
||||
})
|
||||
|
||||
It("should return nil if no patterns match", func() {
|
||||
message := "(XYZ-123)"
|
||||
parsedData := ParseBody(message)
|
||||
Expect(parsedData).To(BeNil())
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,7 +1,17 @@
|
||||
package parser
|
||||
|
||||
import "caatsm/internal/adapter/dto"
|
||||
|
||||
// AviationParser implements the Parser interface
|
||||
type AviationParser struct{}
|
||||
|
||||
// Parse parses a raw message string and returns a ParsedTelegram
|
||||
func (p *AviationParser) Parse(rawText string) (*dto.ParsedTelegram, error) {
|
||||
return Parse(rawText)
|
||||
}
|
||||
|
||||
// ProvideParser creates a parser instance
|
||||
func ProvideParser() Parser {
|
||||
return NewAviationParser()
|
||||
return &AviationParser{}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,221 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"caatsm/internal/domain"
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func ExtractWaypoint(message string) *domain.WayPoint {
|
||||
matches := WaypointExpression.FindStringSubmatch(message)
|
||||
if matches == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
data := make(map[string]string)
|
||||
for i, name := range WaypointExpression.SubexpNames() {
|
||||
if i != 0 && name != "" {
|
||||
data[name] = matches[i]
|
||||
}
|
||||
}
|
||||
result := &domain.WayPoint{
|
||||
ArrivalTime: data[ArrivalTime],
|
||||
Airport: data[AirportCode],
|
||||
DepartureTime: data[DepartureTime],
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func FindDef(code string) *LineParser {
|
||||
// fmt.Printf("Finding definition for %s\n", code)
|
||||
// fmt.Println("ParserDef: ", parserDef)
|
||||
for _, def := range *parserDef {
|
||||
for _, airline := range def.Airlines {
|
||||
if airline == code {
|
||||
return &def
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func standardizeSpaces(s string) string {
|
||||
return strings.Join(strings.Fields(s), " ")
|
||||
}
|
||||
|
||||
func ParseWithDef(line string, parserDef *LineParser) *domain.ScheduleLine {
|
||||
log := zap.S()
|
||||
cleanLine := standardizeSpaces(strings.TrimSpace(line))
|
||||
words := strings.Split(cleanLine, " ")
|
||||
var flightSchedule = &domain.ScheduleLine{
|
||||
Reference: line,
|
||||
}
|
||||
if strings.Contains(line, CANCELLED) {
|
||||
flightSchedule.Comments = "Cancelled"
|
||||
return flightSchedule
|
||||
}
|
||||
// var result map[string]string
|
||||
if parserDef == nil {
|
||||
log.Warnf("No definition found: %s", line)
|
||||
flightSchedule.Comments = "No definition found [" + line + "] "
|
||||
return flightSchedule
|
||||
}
|
||||
if len(words) < parserDef.MinLen {
|
||||
log.Warnf("Line too short: %s", line)
|
||||
flightSchedule.Comments = "Line too short [" + line + "] "
|
||||
return flightSchedule
|
||||
}
|
||||
|
||||
for i, field := range parserDef.Fields {
|
||||
// log.Debugf("Parsing field %v -> %s", i, field)
|
||||
data := extract(words[i], parserMap[field])
|
||||
if data != nil {
|
||||
switch field {
|
||||
case Index:
|
||||
flightSchedule.Index = data[Index]
|
||||
case Date:
|
||||
flightSchedule.Date = data[Date]
|
||||
case Task:
|
||||
flightSchedule.Task = data[Task]
|
||||
case FlightNumber:
|
||||
flightSchedule.FlightNumber = getFlightNumbers(data[FlightNumber])
|
||||
case Register:
|
||||
flightSchedule.AircraftReg = data[Register]
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(words) > parserDef.WaypointStart {
|
||||
flightSchedule.Waypoints, _ = parseWaypoints(words[parserDef.WaypointStart:])
|
||||
} else {
|
||||
log.Warn("No waypoints found")
|
||||
flightSchedule.Comments = "No waypoints found"
|
||||
}
|
||||
|
||||
return flightSchedule
|
||||
}
|
||||
|
||||
// ParseLine processes a single line of schedule data and returns a ScheduleLine object.
|
||||
func ParseLine(line string) (*domain.ScheduleLine, error) {
|
||||
log := zap.S()
|
||||
cleanLine := strings.TrimSpace(line)
|
||||
words := strings.Split(cleanLine, " ")
|
||||
flightSchedule := &domain.ScheduleLine{Reference: line}
|
||||
|
||||
if indexData := extract(words[0], IndexExpression); indexData != nil {
|
||||
flightSchedule.Index = indexData[Index]
|
||||
words = words[1:]
|
||||
}
|
||||
|
||||
parseStrategy := []string{Task, Date, FlightNumber, Register}
|
||||
_, maxParsed, err := parseFields(words, parseStrategy, flightSchedule)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Check if there are any waypoints after the parsed fields
|
||||
if maxParsed+1 < len(words) {
|
||||
waypoints, err := parseWaypoints(words[maxParsed+1:])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
flightSchedule.Waypoints = waypoints
|
||||
} else {
|
||||
log.Warn("No waypoints found")
|
||||
flightSchedule.Comments = "No waypoints found"
|
||||
}
|
||||
|
||||
return flightSchedule, nil
|
||||
}
|
||||
|
||||
// parseFields processes the fields based on the given strategy and updates the flight schedule.
|
||||
func parseFields(words []string, parseStrategy []string, flightSchedule *domain.ScheduleLine) (map[string]bool, int, error) {
|
||||
parsed := make(map[string]bool)
|
||||
var maxParsed int
|
||||
|
||||
for i, word := range words {
|
||||
for _, name := range parseStrategy {
|
||||
if parsed[name] {
|
||||
continue
|
||||
}
|
||||
if data := extract(word, parserMap[name]); data != nil {
|
||||
updateFlightSchedule(flightSchedule, name, data)
|
||||
parsed[name] = true
|
||||
maxParsed = i
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return parsed, maxParsed, nil
|
||||
}
|
||||
|
||||
// updateFlightSchedule updates the flight schedule based on the parsed data.
|
||||
func updateFlightSchedule(flightSchedule *domain.ScheduleLine, name string, data map[string]string) {
|
||||
switch name {
|
||||
case Task:
|
||||
flightSchedule.Task = data[Task]
|
||||
case Date:
|
||||
flightSchedule.Date = data[Date]
|
||||
case FlightNumber:
|
||||
flightSchedule.FlightNumber = getFlightNumbers(data[FlightNumber])
|
||||
case Register:
|
||||
flightSchedule.AircraftReg = data[Register]
|
||||
}
|
||||
}
|
||||
|
||||
// parseWaypoints processes a slice of waypoint strings and returns a slice of WayPoint objects.
|
||||
func parseWaypoints(target []string) ([]domain.WayPoint, error) {
|
||||
log := zap.S()
|
||||
points := getValidPoints(target)
|
||||
if len(points) == 0 {
|
||||
log.Warn("No waypoints found")
|
||||
return nil, errors.New("no waypoints")
|
||||
}
|
||||
var waypoints []domain.WayPoint
|
||||
for i, point := range points {
|
||||
if digits := extract(point, AllDigitsExpression); i > 0 && digits != nil && len(waypoints) > 0 {
|
||||
waypoints[len(waypoints)-1].DepartureTime = digits[DepartureTime]
|
||||
} else if waypoint := ExtractWaypoint(point); waypoint != nil {
|
||||
waypoints = append(waypoints, *waypoint)
|
||||
}
|
||||
}
|
||||
if len(waypoints) == 0 {
|
||||
log.Warn("No waypoints found")
|
||||
return nil, errors.New("no waypoints")
|
||||
}
|
||||
return waypoints, nil
|
||||
}
|
||||
|
||||
func getValidPoints(data []string) []string {
|
||||
var points []string
|
||||
for i, point := range data {
|
||||
if extract(point, WaypointExpression) != nil {
|
||||
return data[i:]
|
||||
}
|
||||
}
|
||||
return points
|
||||
}
|
||||
|
||||
/**
|
||||
* CZ6794/79
|
||||
* CZ3301/2
|
||||
* CA1371/1372/1527
|
||||
*/
|
||||
func getFlightNumbers(data string) []string {
|
||||
if strings.Contains(data, "/") {
|
||||
data := strings.Split(data, "/")
|
||||
baseNumber := data[0]
|
||||
baseLength := len(baseNumber)
|
||||
flightNumbers := append([]string{}, baseNumber)
|
||||
for _, number := range data[1:] {
|
||||
length := len(number)
|
||||
flightNumber := baseNumber[:baseLength-length] + number
|
||||
flightNumbers = append(flightNumbers, flightNumber)
|
||||
}
|
||||
return flightNumbers
|
||||
} else {
|
||||
return []string{data}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,591 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
var _ = Describe("Schedule Parser", func() {
|
||||
|
||||
Describe("Index Parser", func() {
|
||||
Context("parse : 83.", func() {
|
||||
It("should return a valid index", func() {
|
||||
message := "83."
|
||||
data := extract(message, IndexExpression)
|
||||
Expect(data).NotTo(BeNil())
|
||||
Expect(data[Index]).To(Equal("83."))
|
||||
})
|
||||
})
|
||||
|
||||
Context("parse : (21)", func() {
|
||||
It("should return a valid index", func() {
|
||||
message := "(21)"
|
||||
data := extract(message, IndexExpression)
|
||||
Expect(data).NotTo(BeNil())
|
||||
Expect(data[Index]).To(Equal("(21)"))
|
||||
})
|
||||
})
|
||||
|
||||
Context("parse : L59", func() {
|
||||
It("should return a valid index", func() {
|
||||
message := "L59"
|
||||
data := extract(message, IndexExpression)
|
||||
Expect(data).NotTo(BeNil())
|
||||
Expect(data[Index]).To(Equal("L59"))
|
||||
})
|
||||
})
|
||||
|
||||
Context("parse : (205)", func() {
|
||||
It("should return a valid index", func() {
|
||||
message := "(205)"
|
||||
data := extract(message, IndexExpression)
|
||||
Expect(data).NotTo(BeNil())
|
||||
Expect(data[Index]).To(Equal("(205)"))
|
||||
})
|
||||
})
|
||||
|
||||
Context("parse : L01", func() {
|
||||
It("should return a valid index", func() {
|
||||
message := "L01"
|
||||
data := extract(message, IndexExpression)
|
||||
Expect(data).NotTo(BeNil())
|
||||
Expect(data[Index]).To(Equal("L01"))
|
||||
})
|
||||
})
|
||||
|
||||
Context("parse : 01)", func() {
|
||||
It("should return a valid index", func() {
|
||||
message := "01)"
|
||||
data := extract(message, IndexExpression)
|
||||
Expect(data).NotTo(BeNil())
|
||||
Expect(data[Index]).To(Equal("01)"))
|
||||
})
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
Describe("Date Parser", func() {
|
||||
Context("parse : 31OCT", func() {
|
||||
message := "31OCT"
|
||||
data := extract(message, DateExpression)
|
||||
It("should return a valid date", func() {
|
||||
Expect(data).NotTo(BeNil())
|
||||
Expect(data[Date]).To(Equal("31OCT"))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Describe("Flight Number Parser", func() {
|
||||
Context("parse : FM9134", func() {
|
||||
It("should return a valid flight number", func() {
|
||||
message := "FM9134"
|
||||
data := extract(message, FlightNumberExpression)
|
||||
Expect(data).NotTo(BeNil())
|
||||
Expect(data[FlightNumber]).To(Equal("FM9134"))
|
||||
})
|
||||
})
|
||||
|
||||
Context("parse : Y87969", func() {
|
||||
It("should return a valid flight number", func() {
|
||||
message := "Y87969"
|
||||
data := extract(message, FlightNumberExpression)
|
||||
Expect(data).NotTo(BeNil())
|
||||
Expect(data[FlightNumber]).To(Equal("Y87969"))
|
||||
})
|
||||
})
|
||||
|
||||
Context("parse : CK261", func() {
|
||||
It("should return a valid flight number", func() {
|
||||
message := "CK261"
|
||||
data := extract(message, FlightNumberExpression)
|
||||
Expect(data).NotTo(BeNil())
|
||||
Expect(data[FlightNumber]).To(Equal("CK261"))
|
||||
})
|
||||
})
|
||||
|
||||
Context("parse : 9C8812", func() {
|
||||
It("should return a valid flight number", func() {
|
||||
message := "9C8812"
|
||||
data := extract(message, FlightNumberExpression)
|
||||
Expect(data).NotTo(BeNil())
|
||||
Expect(data[FlightNumber]).To(Equal("9C8812"))
|
||||
})
|
||||
})
|
||||
|
||||
Context("CA1371/1372/1527", func() {
|
||||
It("3 number : CA1371 CA1372 CA1527", func() {
|
||||
message := "CA1371/1372/1527"
|
||||
data := getFlightNumbers(message)
|
||||
Expect(data).NotTo(BeNil())
|
||||
Expect(data).To(HaveLen(3))
|
||||
Expect(data).To(ContainElement("CA1371"))
|
||||
Expect(data).To(ContainElement("CA1372"))
|
||||
Expect(data).To(ContainElement("CA1527"))
|
||||
})
|
||||
})
|
||||
|
||||
Context("CZ3301/2", func() {
|
||||
It("2 number : CZ3301 CZ3302", func() {
|
||||
message := "CZ3301/2"
|
||||
data := getFlightNumbers(message)
|
||||
Expect(data).NotTo(BeNil())
|
||||
Expect(data).To(HaveLen(2))
|
||||
Expect(data).To(ContainElement("CZ3301"))
|
||||
Expect(data).To(ContainElement("CZ3302"))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Describe("Schedule Date Parser", func() {
|
||||
Context("parse : 29OCT", func() {
|
||||
It("should return a valid date", func() {
|
||||
message := "29OCT"
|
||||
data := extract(message, DateExpression)
|
||||
Expect(data).NotTo(BeNil())
|
||||
Expect(data[Date]).To(Equal("29OCT"))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Describe("FindWaypoint", func() {
|
||||
It("should return the correct waypoints based on the message", func() {
|
||||
message := "1845(11JUN)TSN/2100"
|
||||
waypoint := ExtractWaypoint(message)
|
||||
Expect(waypoint).NotTo(BeNil())
|
||||
Expect(waypoint.ArrivalTime).To(Equal("1845(11JUN)"))
|
||||
Expect(waypoint.Airport).To(Equal("TSN"))
|
||||
Expect(waypoint.DepartureTime).To(Equal("2100"))
|
||||
})
|
||||
|
||||
It("should return nil if no waypoints are found", func() {
|
||||
message := "18451TSN"
|
||||
waypoints := ExtractWaypoint(message)
|
||||
Expect(waypoints).To(BeNil())
|
||||
})
|
||||
|
||||
It("TSN/0645", func() {
|
||||
message := "TSN/0645"
|
||||
waypoint := ExtractWaypoint(message)
|
||||
Expect(waypoint).NotTo(BeNil())
|
||||
Expect(waypoint.Airport).To(Equal("TSN"))
|
||||
Expect(waypoint.DepartureTime).To(Equal("0645"))
|
||||
})
|
||||
})
|
||||
|
||||
Describe("Waypoints", func() {
|
||||
Context("XIY/0415 TSN/0645 CGQ", func() {
|
||||
It("should return 3 waypoints", func() {
|
||||
points := strings.Split("XIY/0415 TSN/0645 CGQ", " ")
|
||||
waypoints, err := parseWaypoints(points)
|
||||
Expect(Expect(err).NotTo(HaveOccurred()))
|
||||
Expect(waypoints).NotTo(BeNil())
|
||||
Expect(waypoints).To(HaveLen(3))
|
||||
Expect(waypoints[0].Airport).To(Equal("XIY"))
|
||||
Expect(waypoints[0].DepartureTime).To(Equal("0415"))
|
||||
Expect(waypoints[1].Airport).To(Equal("TSN"))
|
||||
Expect(waypoints[1].DepartureTime).To(Equal("0645"))
|
||||
Expect(waypoints[2].Airport).To(Equal("CGQ"))
|
||||
|
||||
})
|
||||
})
|
||||
Context("ICN 0235 TSN", func() {
|
||||
It("should return 2 waypoints", func() {
|
||||
points := strings.Split("ICN 0235 TSN", " ")
|
||||
waypoints, err := parseWaypoints(points)
|
||||
Expect(Expect(err).NotTo(HaveOccurred()))
|
||||
Expect(waypoints).NotTo(BeNil())
|
||||
Expect(waypoints).To(HaveLen(2))
|
||||
Expect(waypoints[0].Airport).To(Equal("ICN"))
|
||||
Expect(waypoints[0].DepartureTime).To(Equal("0235"))
|
||||
Expect(waypoints[1].Airport).To(Equal("TSN"))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
var _ = Describe("Parser Definition", func() {
|
||||
|
||||
Context("MF", func() {
|
||||
It("valid def", func() {
|
||||
def := FindDef("MF")
|
||||
Expect(def).NotTo(BeNil())
|
||||
Expect(def.Airlines).To(ContainElement("MF"))
|
||||
})
|
||||
})
|
||||
Context("FM", func() {
|
||||
It("valid def", func() {
|
||||
def := FindDef("FM")
|
||||
Expect(def).NotTo(BeNil())
|
||||
Expect(def.Airlines).To(ContainElement("FM"))
|
||||
})
|
||||
})
|
||||
|
||||
Context("8X", func() {
|
||||
It("valid def", func() {
|
||||
def := FindDef("8X")
|
||||
Expect(def).NotTo(BeNil())
|
||||
Expect(def.Airlines).To(ContainElement("8X"))
|
||||
})
|
||||
})
|
||||
|
||||
Context("XX", func() {
|
||||
It("nil", func() {
|
||||
def := FindDef("XX")
|
||||
Expect(def).To(BeNil())
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
var _ = Describe("Parse Line with PreDef", func() {
|
||||
Context("FM", func() {
|
||||
It("W/Z FM9134 B2688 1/1ILS (00) TSN0100 SHA", func() {
|
||||
lineText := "W/Z FM9134 B2688 1/1ILS (00) TSN0100 SHA"
|
||||
def := FindDef("FM")
|
||||
schedule := ParseWithDef(lineText, def)
|
||||
Expect(schedule).NotTo(BeNil())
|
||||
Expect(schedule.Task).To(Equal("W/Z"))
|
||||
Expect(len(schedule.FlightNumber)).To(Equal(1))
|
||||
Expect(schedule.FlightNumber[0]).To(Equal("FM9134"))
|
||||
Expect(schedule.AircraftReg).To(Equal("B2688"))
|
||||
Expect(len(schedule.Waypoints)).To(Equal(2))
|
||||
Expect(schedule.Waypoints[0].Airport).To(Equal("TSN"))
|
||||
Expect(schedule.Waypoints[0].DepartureTime).To(Equal("0100"))
|
||||
Expect(schedule.Waypoints[1].Airport).To(Equal("SHA"))
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
Context("MF", func() {
|
||||
It("01) MF8193 B5595 ILS(8) HGH1100 1305TSN", func() {
|
||||
lineText := "01) MF8193 B5595 ILS(8) HGH1100 1305TSN"
|
||||
def := FindDef("MF")
|
||||
Expect(def).NotTo(BeNil())
|
||||
schedule := ParseWithDef(lineText, def)
|
||||
Expect(schedule).NotTo(BeNil())
|
||||
Expect(schedule.Index).To(Equal("01)"))
|
||||
Expect(len(schedule.FlightNumber)).To(Equal(1))
|
||||
Expect(schedule.FlightNumber[0]).To(Equal("MF8193"))
|
||||
Expect(schedule.AircraftReg).To(Equal("B5595"))
|
||||
Expect(len(schedule.Waypoints)).To(Equal(2))
|
||||
Expect(schedule.Waypoints[0].Airport).To(Equal("HGH"))
|
||||
Expect(schedule.Waypoints[0].DepartureTime).To(Equal("1100"))
|
||||
Expect(schedule.Waypoints[1].Airport).To(Equal("TSN"))
|
||||
Expect(schedule.Waypoints[1].ArrivalTime).To(Equal("1305"))
|
||||
})
|
||||
})
|
||||
|
||||
Context("8X", func() {
|
||||
It("L1: 29OCT BK2735 B2863 ILS IS (3/6) TSN2350(28OCT) HAK", func() {
|
||||
lineText := "L1: 29OCT BK2735 B2863 ILS IS (3/6) TSN2350(28OCT) HAK"
|
||||
def := FindDef("8X")
|
||||
Expect(def).NotTo(BeNil())
|
||||
schedule := ParseWithDef(lineText, def)
|
||||
Expect(schedule).NotTo(BeNil())
|
||||
Expect(schedule.Index).To(Equal("L1:"))
|
||||
Expect(schedule.Date).To(Equal("29OCT"))
|
||||
Expect(len(schedule.FlightNumber)).To(Equal(1))
|
||||
Expect(schedule.FlightNumber[0]).To(Equal("BK2735"))
|
||||
Expect(schedule.AircraftReg).To(Equal("B2863"))
|
||||
Expect(len(schedule.Waypoints)).To(Equal(2))
|
||||
Expect(schedule.Waypoints[0].Airport).To(Equal("TSN"))
|
||||
Expect(schedule.Waypoints[0].DepartureTime).To(Equal("2350(28OCT)"))
|
||||
Expect(schedule.Waypoints[1].Airport).To(Equal("HAK"))
|
||||
})
|
||||
})
|
||||
|
||||
Context("HU", func() {
|
||||
It("L05 W/Z HU7205 B5406 (9) TSN/2355(30OCT) PVG", func() {
|
||||
lineText := "L05 W/Z HU7205 B5406 (9) TSN/2355(30OCT) PVG"
|
||||
def := FindDef("HU")
|
||||
Expect(def).NotTo(BeNil())
|
||||
schedule := ParseWithDef(lineText, def)
|
||||
Expect(schedule).NotTo(BeNil())
|
||||
Expect(schedule.Index).To(Equal("L05"))
|
||||
Expect(schedule.Task).To(Equal("W/Z"))
|
||||
Expect(len(schedule.FlightNumber)).To(Equal(1))
|
||||
Expect(schedule.FlightNumber[0]).To(Equal("HU7205"))
|
||||
Expect(schedule.AircraftReg).To(Equal("B5406"))
|
||||
Expect(len(schedule.Waypoints)).To(Equal(2))
|
||||
Expect(schedule.Waypoints[0].Airport).To(Equal("TSN"))
|
||||
Expect(schedule.Waypoints[0].DepartureTime).To(Equal("2355(30OCT)"))
|
||||
Expect(schedule.Waypoints[1].Airport).To(Equal("PVG"))
|
||||
})
|
||||
})
|
||||
|
||||
Context("JD", func() {
|
||||
It("1) JD5195 B6727 ILS I(9) SYX/0800 1135/TSN", func() {
|
||||
lineText := "1) JD5195 B6727 ILS I(9) SYX/0800 1135/TSN"
|
||||
def := FindDef("JD")
|
||||
Expect(def).NotTo(BeNil())
|
||||
schedule := ParseWithDef(lineText, def)
|
||||
Expect(schedule).NotTo(BeNil())
|
||||
Expect(schedule.Index).To(Equal("1)"))
|
||||
Expect(len(schedule.FlightNumber)).To(Equal(1))
|
||||
Expect(schedule.FlightNumber[0]).To(Equal("JD5195"))
|
||||
Expect(schedule.AircraftReg).To(Equal("B6727"))
|
||||
Expect(len(schedule.Waypoints)).To(Equal(2))
|
||||
Expect(schedule.Waypoints[0].Airport).To(Equal("SYX"))
|
||||
Expect(schedule.Waypoints[0].DepartureTime).To(Equal("0800"))
|
||||
Expect(schedule.Waypoints[1].Airport).To(Equal("TSN"))
|
||||
Expect(schedule.Waypoints[1].ArrivalTime).To(Equal("1135"))
|
||||
})
|
||||
})
|
||||
|
||||
Context("GS", func() {
|
||||
It("01 GS7635 B3193 XIY0020(16APR) CGD", func() {
|
||||
lineText := "01 GS7635 B3193 XIY0020(16APR) CGD"
|
||||
def := FindDef("GS")
|
||||
Expect(def).NotTo(BeNil())
|
||||
schedule := ParseWithDef(lineText, def)
|
||||
Expect(schedule).NotTo(BeNil())
|
||||
Expect(schedule.Index).To(Equal("01"))
|
||||
Expect(len(schedule.FlightNumber)).To(Equal(1))
|
||||
Expect(schedule.FlightNumber[0]).To(Equal("GS7635"))
|
||||
Expect(schedule.AircraftReg).To(Equal("B3193"))
|
||||
Expect(len(schedule.Waypoints)).To(Equal(2))
|
||||
Expect(schedule.Waypoints[0].Airport).To(Equal("XIY"))
|
||||
Expect(schedule.Waypoints[0].DepartureTime).To(Equal("0020(16APR)"))
|
||||
Expect(schedule.Waypoints[1].Airport).To(Equal("CGD"))
|
||||
})
|
||||
})
|
||||
|
||||
Context("Y8", func() {
|
||||
It("13 Y87444 B2578 ICN 0235 TSN", func() {
|
||||
lineText := "13 Y87444 B2578 ICN 0235 TSN"
|
||||
def := FindDef("Y8")
|
||||
Expect(def).NotTo(BeNil())
|
||||
schedule := ParseWithDef(lineText, def)
|
||||
Expect(schedule).NotTo(BeNil())
|
||||
Expect(schedule.Index).To(Equal("13"))
|
||||
Expect(len(schedule.FlightNumber)).To(Equal(1))
|
||||
Expect(schedule.FlightNumber[0]).To(Equal("Y87444"))
|
||||
Expect(schedule.AircraftReg).To(Equal("B2578"))
|
||||
Expect(len(schedule.Waypoints)).To(Equal(2))
|
||||
Expect(schedule.Waypoints[0].Airport).To(Equal("ICN"))
|
||||
Expect(schedule.Waypoints[0].DepartureTime).To(Equal("0235"))
|
||||
Expect(schedule.Waypoints[1].Airport).To(Equal("TSN"))
|
||||
})
|
||||
})
|
||||
|
||||
Context("3U", func() {
|
||||
It("01) 31OCT 3U8863 B6598 CAT1 (10) CKG0010 0235TSN", func() {
|
||||
lineText := "01) 31OCT 3U8863 B6598 CAT1 (10) CKG0010 0235TSN"
|
||||
def := FindDef("3U")
|
||||
Expect(def).NotTo(BeNil())
|
||||
schedule := ParseWithDef(lineText, def)
|
||||
Expect(schedule).NotTo(BeNil())
|
||||
Expect(schedule.Index).To(Equal("01)"))
|
||||
Expect(schedule.Date).To(Equal("31OCT"))
|
||||
Expect(len(schedule.FlightNumber)).To(Equal(1))
|
||||
Expect(schedule.FlightNumber[0]).To(Equal("3U8863"))
|
||||
Expect(schedule.AircraftReg).To(Equal("B6598"))
|
||||
Expect(len(schedule.Waypoints)).To(Equal(2))
|
||||
Expect(schedule.Waypoints[0].Airport).To(Equal("CKG"))
|
||||
Expect(schedule.Waypoints[0].DepartureTime).To(Equal("0010"))
|
||||
Expect(schedule.Waypoints[1].Airport).To(Equal("TSN"))
|
||||
Expect(schedule.Waypoints[1].ArrivalTime).To(Equal("0235"))
|
||||
})
|
||||
})
|
||||
|
||||
Context("CK", func() {
|
||||
It("01)H/Z CK261 B2076 PVG1535(30OCT) 1705TPE", func() {
|
||||
lineText := "01)H/Z CK261 B2076 PVG1535(30OCT) 1705TPE"
|
||||
def := FindDef("CK")
|
||||
Expect(def).NotTo(BeNil())
|
||||
schedule := ParseWithDef(lineText, def)
|
||||
Expect(schedule).NotTo(BeNil())
|
||||
Expect(schedule.Task).To(Equal("H/Z"))
|
||||
Expect(len(schedule.FlightNumber)).To(Equal(1))
|
||||
Expect(schedule.FlightNumber[0]).To(Equal("CK261"))
|
||||
Expect(schedule.AircraftReg).To(Equal("B2076"))
|
||||
Expect(len(schedule.Waypoints)).To(Equal(2))
|
||||
Expect(schedule.Waypoints[0].Airport).To(Equal("PVG"))
|
||||
Expect(schedule.Waypoints[0].DepartureTime).To(Equal("1535(30OCT)"))
|
||||
Expect(schedule.Waypoints[1].Airport).To(Equal("TPE"))
|
||||
Expect(schedule.Waypoints[1].ArrivalTime).To(Equal("1705"))
|
||||
})
|
||||
})
|
||||
|
||||
Context("G5", func() {
|
||||
It("L01 W/Z G52665 B7762 (6) CKG/0725 CIH/0940 TSN", func() {
|
||||
lineText := "L01 W/Z G52665 B7762 (6) CKG/0725 CIH/0940 TSN"
|
||||
def := FindDef("G5")
|
||||
Expect(def).NotTo(BeNil())
|
||||
schedule := ParseWithDef(lineText, def)
|
||||
Expect(schedule).NotTo(BeNil())
|
||||
Expect(schedule.Index).To(Equal("L01"))
|
||||
Expect(schedule.Task).To(Equal("W/Z"))
|
||||
Expect(len(schedule.FlightNumber)).To(Equal(1))
|
||||
Expect(schedule.FlightNumber[0]).To(Equal("G52665"))
|
||||
Expect(schedule.AircraftReg).To(Equal("B7762"))
|
||||
Expect(len(schedule.Waypoints)).To(Equal(3))
|
||||
Expect(schedule.Waypoints[0].Airport).To(Equal("CKG"))
|
||||
Expect(schedule.Waypoints[0].DepartureTime).To(Equal("0725"))
|
||||
Expect(schedule.Waypoints[1].Airport).To(Equal("CIH"))
|
||||
Expect(schedule.Waypoints[1].DepartureTime).To(Equal("0940"))
|
||||
Expect(schedule.Waypoints[2].Airport).To(Equal("TSN"))
|
||||
})
|
||||
})
|
||||
|
||||
Context("9C", func() {
|
||||
It("31OCT W/Z 9C8884 B6573 ILS1/1 (06) TSN0650 SYX", func() {
|
||||
lineText := "31OCT W/Z 9C8884 B6573 ILS1/1 (06) TSN0650 SYX"
|
||||
def := FindDef("9C")
|
||||
Expect(def).NotTo(BeNil())
|
||||
schedule := ParseWithDef(lineText, def)
|
||||
Expect(schedule).NotTo(BeNil())
|
||||
Expect(schedule.Date).To(Equal("31OCT"))
|
||||
Expect(schedule.Task).To(Equal("W/Z"))
|
||||
Expect(len(schedule.FlightNumber)).To(Equal(1))
|
||||
Expect(schedule.FlightNumber[0]).To(Equal("9C8884"))
|
||||
Expect(schedule.AircraftReg).To(Equal("B6573"))
|
||||
Expect(len(schedule.Waypoints)).To(Equal(2))
|
||||
Expect(schedule.Waypoints[0].Airport).To(Equal("TSN"))
|
||||
Expect(schedule.Waypoints[0].DepartureTime).To(Equal("0650"))
|
||||
Expect(schedule.Waypoints[1].Airport).To(Equal("SYX"))
|
||||
})
|
||||
})
|
||||
|
||||
Context("ZH", func() {
|
||||
It("204) W/Z 31OCT ZH9783 B5670 CAT1 (10) SZX0045 0355TSN", func() {
|
||||
lineText := "204) W/Z 31OCT ZH9783 B5670 CAT1 (10) SZX0045 0355TSN"
|
||||
def := FindDef("ZH")
|
||||
Expect(def).NotTo(BeNil())
|
||||
schedule := ParseWithDef(lineText, def)
|
||||
Expect(schedule).NotTo(BeNil())
|
||||
Expect(schedule.Index).To(Equal("204)"))
|
||||
Expect(schedule.Task).To(Equal("W/Z"))
|
||||
Expect(len(schedule.FlightNumber)).To(Equal(1))
|
||||
Expect(schedule.FlightNumber[0]).To(Equal("ZH9783"))
|
||||
Expect(schedule.AircraftReg).To(Equal("B5670"))
|
||||
Expect(len(schedule.Waypoints)).To(Equal(2))
|
||||
Expect(schedule.Waypoints[0].Airport).To(Equal("SZX"))
|
||||
Expect(schedule.Waypoints[0].DepartureTime).To(Equal("0045"))
|
||||
Expect(schedule.Waypoints[1].Airport).To(Equal("TSN"))
|
||||
Expect(schedule.Waypoints[1].ArrivalTime).To(Equal("0355"))
|
||||
})
|
||||
})
|
||||
|
||||
Context("8L", func() {
|
||||
It("L59 W/Z 8L9976 B6959 TSN/0510 CTU/0855 KMG", func() {
|
||||
lineText := "L59 W/Z 8L9976 B6959 TSN/0510 CTU/0855 KMG"
|
||||
def := FindDef("8L")
|
||||
Expect(def).NotTo(BeNil())
|
||||
schedule := ParseWithDef(lineText, def)
|
||||
Expect(schedule).NotTo(BeNil())
|
||||
Expect(schedule.Index).To(Equal("L59"))
|
||||
Expect(schedule.Task).To(Equal("W/Z"))
|
||||
Expect(len(schedule.FlightNumber)).To(Equal(1))
|
||||
Expect(schedule.FlightNumber[0]).To(Equal("8L9976"))
|
||||
Expect(schedule.AircraftReg).To(Equal("B6959"))
|
||||
Expect(len(schedule.Waypoints)).To(Equal(3))
|
||||
Expect(schedule.Waypoints[0].Airport).To(Equal("TSN"))
|
||||
Expect(schedule.Waypoints[0].DepartureTime).To(Equal("0510"))
|
||||
Expect(schedule.Waypoints[1].Airport).To(Equal("CTU"))
|
||||
Expect(schedule.Waypoints[1].DepartureTime).To(Equal("0855"))
|
||||
Expect(schedule.Waypoints[2].Airport).To(Equal("KMG"))
|
||||
})
|
||||
})
|
||||
|
||||
Context("SC", func() {
|
||||
It("(1) SC4717 B3080 CRJ7 ILS I (6) TAO/2350 TSN", func() {
|
||||
lineText := "(1) SC4717 B3080 CRJ7 ILS I (6) TAO/2350 TSN"
|
||||
def := FindDef("SC")
|
||||
Expect(def).NotTo(BeNil())
|
||||
schedule := ParseWithDef(lineText, def)
|
||||
Expect(schedule).NotTo(BeNil())
|
||||
Expect(schedule.Index).To(Equal("(1)"))
|
||||
Expect(len(schedule.FlightNumber)).To(Equal(1))
|
||||
Expect(schedule.FlightNumber[0]).To(Equal("SC4717"))
|
||||
Expect(schedule.AircraftReg).To(Equal("B3080"))
|
||||
Expect(len(schedule.Waypoints)).To(Equal(2))
|
||||
Expect(schedule.Waypoints[0].Airport).To(Equal("TAO"))
|
||||
Expect(schedule.Waypoints[0].DepartureTime).To(Equal("2350"))
|
||||
Expect(schedule.Waypoints[1].Airport).To(Equal("TSN"))
|
||||
})
|
||||
})
|
||||
|
||||
Context("CZ", func() {
|
||||
It("83. CZ3301/2 B2823 B752 CAN0135 TSN0535 CAN", func() {
|
||||
lineText := "83. CZ3301/2 B2823 B752 CAN0135 TSN0535 CAN"
|
||||
def := FindDef("CZ")
|
||||
Expect(def).NotTo(BeNil())
|
||||
schedule := ParseWithDef(lineText, def)
|
||||
Expect(schedule).NotTo(BeNil())
|
||||
Expect(schedule.Index).To(Equal("83."))
|
||||
Expect(len(schedule.FlightNumber)).To(Equal(2))
|
||||
Expect(schedule.FlightNumber).To(ContainElement("CZ3301"))
|
||||
Expect(schedule.FlightNumber).To(ContainElement("CZ3302"))
|
||||
Expect(schedule.AircraftReg).To(Equal("B2823"))
|
||||
Expect(len(schedule.Waypoints)).To(Equal(3))
|
||||
Expect(schedule.Waypoints[0].Airport).To(Equal("CAN"))
|
||||
Expect(schedule.Waypoints[0].DepartureTime).To(Equal("0135"))
|
||||
Expect(schedule.Waypoints[1].Airport).To(Equal("TSN"))
|
||||
Expect(schedule.Waypoints[1].DepartureTime).To(Equal("0535"))
|
||||
Expect(schedule.Waypoints[2].Airport).To(Equal("CAN"))
|
||||
})
|
||||
|
||||
It("83. CZ3301/2 B2823 B752 CAN0135 TSN0535 CAN", func() {
|
||||
lineText := "83. CZ3301/2 B2823 B752 CAN0135 TSN0535 CAN"
|
||||
def := FindDef("CZ")
|
||||
Expect(def).NotTo(BeNil())
|
||||
schedule := ParseWithDef(lineText, def)
|
||||
Expect(schedule).NotTo(BeNil())
|
||||
Expect(schedule.Index).To(Equal("83."))
|
||||
Expect(len(schedule.FlightNumber)).To(Equal(2))
|
||||
Expect(schedule.FlightNumber).To(ContainElement("CZ3301"))
|
||||
Expect(schedule.FlightNumber).To(ContainElement("CZ3302"))
|
||||
Expect(schedule.AircraftReg).To(Equal("B2823"))
|
||||
Expect(len(schedule.Waypoints)).To(Equal(3))
|
||||
Expect(schedule.Waypoints[0].Airport).To(Equal("CAN"))
|
||||
Expect(schedule.Waypoints[0].DepartureTime).To(Equal("0135"))
|
||||
Expect(schedule.Waypoints[1].Airport).To(Equal("TSN"))
|
||||
Expect(schedule.Waypoints[1].DepartureTime).To(Equal("0535"))
|
||||
Expect(schedule.Waypoints[2].Airport).To(Equal("CAN"))
|
||||
})
|
||||
})
|
||||
|
||||
Context("HO", func() {
|
||||
It("124) 14NOV W/Z HO1245 B6966 ILS(8) SHA0005 0255CKG", func() {
|
||||
lineText := "124) 14NOV W/Z HO1245 B6966 ILS(8) SHA0005 0255CKG"
|
||||
def := FindDef("HO")
|
||||
Expect(def).NotTo(BeNil())
|
||||
schedule := ParseWithDef(lineText, def)
|
||||
Expect(schedule).NotTo(BeNil())
|
||||
Expect(schedule.Index).To(Equal("124)"))
|
||||
Expect(schedule.Task).To(Equal("W/Z"))
|
||||
Expect(len(schedule.FlightNumber)).To(Equal(1))
|
||||
Expect(schedule.FlightNumber[0]).To(Equal("HO1245"))
|
||||
Expect(schedule.AircraftReg).To(Equal("B6966"))
|
||||
Expect(len(schedule.Waypoints)).To(Equal(2))
|
||||
Expect(schedule.Waypoints[0].Airport).To(Equal("SHA"))
|
||||
Expect(schedule.Waypoints[0].DepartureTime).To(Equal("0005"))
|
||||
Expect(schedule.Waypoints[1].Airport).To(Equal("CKG"))
|
||||
Expect(schedule.Waypoints[1].ArrivalTime).To(Equal("0255"))
|
||||
})
|
||||
})
|
||||
|
||||
Context("EU", func() {
|
||||
It("38)W/Z 27NOV EU2748 B6900 CAT1 (8) TSN0800 1055CTU", func() {
|
||||
lineText := "38)W/Z 27NOV EU2748 B6900 CAT1 (8) TSN0800 1055CTU"
|
||||
def := FindDef("EU")
|
||||
Expect(def).NotTo(BeNil())
|
||||
schedule := ParseWithDef(lineText, def)
|
||||
Expect(schedule).NotTo(BeNil())
|
||||
// Expect(schedule.Index).To(Equal("38)"))
|
||||
Expect(schedule.Task).To(Equal("W/Z"))
|
||||
Expect(len(schedule.FlightNumber)).To(Equal(1))
|
||||
Expect(schedule.FlightNumber[0]).To(Equal("EU2748"))
|
||||
Expect(schedule.AircraftReg).To(Equal("B6900"))
|
||||
Expect(len(schedule.Waypoints)).To(Equal(2))
|
||||
Expect(schedule.Waypoints[0].Airport).To(Equal("TSN"))
|
||||
Expect(schedule.Waypoints[0].DepartureTime).To(Equal("0800"))
|
||||
Expect(schedule.Waypoints[1].Airport).To(Equal("CTU"))
|
||||
Expect(schedule.Waypoints[1].ArrivalTime).To(Equal("1055"))
|
||||
})
|
||||
})
|
||||
|
||||
})
|
||||
@@ -0,0 +1,13 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
func TestParsers(t *testing.T) {
|
||||
RegisterFailHandler(Fail)
|
||||
RunSpecs(t, "Parsers Suite")
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
package adapter
|
||||
|
||||
// Publisher defines the interface for publishing parsed messages
|
||||
type Publisher interface {
|
||||
// Publish publishes a parsed message
|
||||
Publish(message interface{}) error
|
||||
}
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
package adapter
|
||||
|
||||
import (
|
||||
"context"
|
||||
"caatsm/internal/model"
|
||||
)
|
||||
|
||||
// Repository defines the interface for message persistence
|
||||
type Repository interface {
|
||||
// InsertOne inserts a single telegram message
|
||||
InsertOne(ctx context.Context, msg *model.ParsedTelegram) error
|
||||
|
||||
// InsertBatch inserts multiple telegram messages in a batch
|
||||
InsertBatch(ctx context.Context, msgs []*model.ParsedTelegram) error
|
||||
|
||||
// InsertRaw captures an unparsed or failed telegram for later analysis.
|
||||
InsertRaw(ctx context.Context, msg *model.ParsedTelegram) error
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user