refactor: Update ARR body parsing logic
The code changes in `aviation_parser_test.go` update the ARR body parsing logic. The `ParseHeader` function now correctly handles ARR bodies with optional time components. This ensures accurate parsing of ARR bodies and improves the overall functionality of the aviation parser.
This commit is contained in:
+107
-5
@@ -1,6 +1,7 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"caatsm/pkg/utils"
|
||||
"fmt"
|
||||
"os"
|
||||
"regexp"
|
||||
@@ -8,6 +9,9 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
"go.uber.org/zap"
|
||||
"go.uber.org/zap/zapcore"
|
||||
"gopkg.in/natefinch/lumberjack.v2"
|
||||
)
|
||||
|
||||
var MyConfig *Config
|
||||
@@ -30,10 +34,10 @@ type SubscriptionConfig struct {
|
||||
}
|
||||
|
||||
type TimeoutsConfig struct {
|
||||
ServerTimeout time.Duration `mapstructure:"server_timeout"`
|
||||
ReconnectWait time.Duration `mapstructure:"reconnect_wait"`
|
||||
CloseTimeout time.Duration `mapstructure:"close_timeout"`
|
||||
AckWaitTimeout time.Duration `mapstructure:"ack_wait_timeout"`
|
||||
Server time.Duration `mapstructure:"server"`
|
||||
ReconnectWait time.Duration `mapstructure:"reconnect_wait"`
|
||||
Close time.Duration `mapstructure:"close"`
|
||||
AckWait time.Duration `mapstructure:"ack_wait"`
|
||||
}
|
||||
|
||||
type BodyConfig struct {
|
||||
@@ -46,7 +50,24 @@ type PatternConfig struct {
|
||||
Expression *regexp.Regexp
|
||||
}
|
||||
|
||||
// LoggerConfig represents the configuration for the logger.
|
||||
type LoggerConfig struct {
|
||||
ZapConfig zap.Config `json:"zapConfig"`
|
||||
LumberjackConfig LumberjackConfig `json:"lumberjackConfig"`
|
||||
}
|
||||
|
||||
// LumberjackConfig represents the configuration for lumberjack logging.
|
||||
type LumberjackConfig struct {
|
||||
Filename string `json:"filename"`
|
||||
MaxSize int `json:"maxSize"`
|
||||
MaxBackups int `json:"maxBackups"`
|
||||
MaxAge int `json:"maxAge"`
|
||||
Compress bool `json:"compress"`
|
||||
}
|
||||
|
||||
const (
|
||||
EnvProd = "prod"
|
||||
// logConfigFile = "configs/log_config.json"
|
||||
// arrPatternString represents the regular expression pattern used to match arrival patterns.
|
||||
// The pattern matches strings in the format: "(TYPE-NUMBER-SSR-DEPARTURE-ARRIVAL)".
|
||||
// The pattern captures the following named groups:
|
||||
@@ -66,7 +87,7 @@ const (
|
||||
// - departure: the four-letter departure airport code
|
||||
// - departure_time: the four-digit departure time
|
||||
// - arrival: the four-letter arrival airport code
|
||||
depPatternString = `^\((?P<category>[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})\)$`
|
||||
depPatternString = `^\((?P<category>[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})\)$`
|
||||
|
||||
// fplPatternString is a regular expression designed to parse and extract detailed information from formatted flight plan strings.
|
||||
// The flight plan string is expected to follow a specific format, encapsulated by parentheses and containing various segments separated by hyphens.
|
||||
@@ -185,6 +206,7 @@ func LoadConfig() (*Config, error) {
|
||||
// log.Error(errMsg)
|
||||
return nil, fmt.Errorf(errMsg)
|
||||
}
|
||||
// loadLoggerConfig()
|
||||
|
||||
// log.Debugf("Config loaded: %+v", config)
|
||||
return &config, nil
|
||||
@@ -206,3 +228,83 @@ func ValidateConfig(cfg *Config) error {
|
||||
// fmt.Println("config validation passed")
|
||||
return nil
|
||||
}
|
||||
|
||||
func loadLoggerConfig() {
|
||||
// log := utils.Logger
|
||||
env := os.Getenv("GO_ENV")
|
||||
if env == "" {
|
||||
env = "dev"
|
||||
}
|
||||
// log.Infof("Environment: %s", env)
|
||||
|
||||
viper.SetConfigType("json")
|
||||
viper.SetConfigName("logger." + env)
|
||||
viper.AddConfigPath("configs")
|
||||
// viper.SetEnvPrefix("tele")
|
||||
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
|
||||
|
||||
if err := viper.ReadInConfig(); err != nil {
|
||||
errMsg := fmt.Sprintf("error reading logger config file for environment '%s': %v", env, err)
|
||||
// log.Error(errMsg)
|
||||
panic(errMsg)
|
||||
}
|
||||
|
||||
// log.Debug("Logger config file read successfully")
|
||||
// log.Debugf("Logger config keys: %v", viper.AllKeys())
|
||||
|
||||
var config LoggerConfig
|
||||
if err := viper.Unmarshal(&config); err != nil {
|
||||
errMsg := fmt.Sprintf("unable to decode logger config into struct for environment '%s': %v", env, err)
|
||||
// log.Error(errMsg)
|
||||
// return nil, fmt.Errorf(errMsg)
|
||||
panic(errMsg)
|
||||
}
|
||||
var logWriter zapcore.WriteSyncer
|
||||
if env == EnvProd {
|
||||
logWriter = zapcore.AddSync(&lumberjack.Logger{
|
||||
Filename: config.LumberjackConfig.Filename,
|
||||
MaxSize: config.LumberjackConfig.MaxSize,
|
||||
MaxBackups: config.LumberjackConfig.MaxBackups,
|
||||
MaxAge: config.LumberjackConfig.MaxAge,
|
||||
Compress: config.LumberjackConfig.Compress,
|
||||
})
|
||||
} else {
|
||||
logWriter = zapcore.AddSync(os.Stdout)
|
||||
}
|
||||
|
||||
encoder := zapcore.NewJSONEncoder(config.ZapConfig.EncoderConfig)
|
||||
level := parseLogLevel(config.ZapConfig.Level.String())
|
||||
|
||||
core := zapcore.NewCore(
|
||||
encoder,
|
||||
logWriter,
|
||||
level,
|
||||
)
|
||||
|
||||
log := zap.New(core, zap.AddCaller(), zap.AddStacktrace(zapcore.ErrorLevel))
|
||||
utils.Logger = log.Sugar()
|
||||
// log.Debugf("Logger config loaded: %+v", config)
|
||||
|
||||
}
|
||||
|
||||
// parseLogLevel converts the log level string to zapcore.Level.
|
||||
func parseLogLevel(level string) zapcore.Level {
|
||||
switch level {
|
||||
case "debug":
|
||||
return zapcore.DebugLevel
|
||||
case "info":
|
||||
return zapcore.InfoLevel
|
||||
case "warn":
|
||||
return zapcore.WarnLevel
|
||||
case "error":
|
||||
return zapcore.ErrorLevel
|
||||
case "dpanic":
|
||||
return zapcore.DPanicLevel
|
||||
case "panic":
|
||||
return zapcore.PanicLevel
|
||||
case "fatal":
|
||||
return zapcore.FatalLevel
|
||||
default:
|
||||
return zapcore.InfoLevel
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ func (n *NatsHandler) Subscribe() {
|
||||
logger := watermill.NewStdLogger(false, false)
|
||||
options := []nc.Option{
|
||||
nc.RetryOnFailedConnect(true),
|
||||
nc.Timeout(n.config.Timeouts.ServerTimeout),
|
||||
nc.Timeout(n.config.Timeouts.Server),
|
||||
nc.ReconnectWait(n.config.Timeouts.ReconnectWait),
|
||||
}
|
||||
jsConfig := nats.JetStreamConfig{Disabled: true}
|
||||
@@ -35,8 +35,8 @@ func (n *NatsHandler) Subscribe() {
|
||||
subscriber, err := nats.NewSubscriber(
|
||||
nats.SubscriberConfig{
|
||||
URL: n.config.Nats.URL,
|
||||
CloseTimeout: n.config.Timeouts.CloseTimeout,
|
||||
AckWaitTimeout: n.config.Timeouts.AckWaitTimeout,
|
||||
CloseTimeout: n.config.Timeouts.Close,
|
||||
AckWaitTimeout: n.config.Timeouts.AckWait,
|
||||
NatsOptions: options,
|
||||
Unmarshaler: marshaler,
|
||||
JetStream: jsConfig,
|
||||
|
||||
@@ -3,7 +3,6 @@ package parsers
|
||||
import (
|
||||
"caatsm/internal/config"
|
||||
"caatsm/internal/domain"
|
||||
"caatsm/pkg/utils"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
@@ -17,9 +16,9 @@ const (
|
||||
)
|
||||
|
||||
var (
|
||||
categoryRegex = regexp.MustCompile(`\(([A-Z]{3})(.*)\)`)
|
||||
categoryRegex = regexp.MustCompile(`\((?P<category>[A-Z]+)-`)
|
||||
emptyLineRemove = regexp.MustCompile(`(?m)^\s*$`)
|
||||
bodyOnly = regexp.MustCompile(`^(ZCZC(.|\n)*)NNNN$`)
|
||||
bodyOnly = regexp.MustCompile(`(.|\n)?((ZCZC(.|\n)*))NNNN$`)
|
||||
)
|
||||
|
||||
type BodyParser struct {
|
||||
@@ -43,30 +42,30 @@ func (bp *BodyParser) SetBodyPatterns(patterns map[string]config.BodyConfig) {
|
||||
|
||||
// Parse attempts to parse the body text using the configured patterns.
|
||||
func (bp *BodyParser) Parse(body string) (interface{}, error) {
|
||||
log := utils.Logger
|
||||
// log := utils.Logger
|
||||
body = strings.TrimSpace(body)
|
||||
log.Info("Parsing body text", body)
|
||||
// log.Info("Parsing body text", body)
|
||||
category := findCategory(body)
|
||||
if category == "" {
|
||||
log.Error("No category found in body text")
|
||||
// log.Error("No category found in body text")
|
||||
return nil, fmt.Errorf("no category found in body text")
|
||||
}
|
||||
patters := bp.GetBodyPatterns()
|
||||
log.Infof("body config [%s] %v\n", category, patters[category])
|
||||
// log.Infof("body config [%s] %v\n", category, patters[category])
|
||||
if patterConfig := patters[category]; patterConfig.Patterns != nil {
|
||||
|
||||
for _, p := range patterConfig.Patterns {
|
||||
log.Infof("Trying pattern %s\n%s\n", p.Comments, p.Pattern)
|
||||
// log.Infof("Trying pattern %s\n%s\n", p.Comments, p.Pattern)
|
||||
|
||||
re := p.Expression
|
||||
match := re.FindStringSubmatch(body)
|
||||
log.Info("Match: ", match)
|
||||
// log.Info("Match: ", match)
|
||||
if match != nil {
|
||||
log.Infof("Matched: %v\n", match)
|
||||
// log.Infof("Matched: %v\n", match)
|
||||
data := extractData(match, re)
|
||||
return createBodyData(data)
|
||||
}
|
||||
log.Infof("No match for pattern %s\n", p.Comments)
|
||||
// log.Infof("No match for pattern %s\n", p.Comments)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -75,9 +74,14 @@ func (bp *BodyParser) Parse(body string) (interface{}, error) {
|
||||
|
||||
func findCategory(body string) string {
|
||||
match := categoryRegex.FindStringSubmatch(body)
|
||||
utils.Logger.Infof("Match: %v\n", match)
|
||||
// utils.Logger.Infof("Match: %v\n", match)
|
||||
if match != nil {
|
||||
return match[1]
|
||||
groups := categoryRegex.SubexpNames()
|
||||
for i, name := range groups {
|
||||
if i != 0 && name == "category" {
|
||||
return match[i]
|
||||
}
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
@@ -168,7 +172,7 @@ func clean(text string) string {
|
||||
if bodyOnly != nil {
|
||||
match := bodyOnly.FindStringSubmatch(cleanText)
|
||||
if len(match) > 1 {
|
||||
bodyOnly := match[1]
|
||||
bodyOnly := match[2]
|
||||
if bodyOnly[len(bodyOnly)-1] == '\n' {
|
||||
return bodyOnly[:len(bodyOnly)-1]
|
||||
}
|
||||
|
||||
@@ -109,44 +109,6 @@ NNNN`
|
||||
|
||||
})
|
||||
|
||||
Context("with NOTAM message", func() {
|
||||
It("should parse the header correctly", func() {
|
||||
message := `
|
||||
ZCZC NOTAM1234 230715
|
||||
GG EDDNZEZN
|
||||
.
|
||||
GG EDDNYNYX
|
||||
.BERLINTWR 230714
|
||||
|
||||
Q) EDMM/QOATT/IV/BO/A/000/999/4814N01120E005
|
||||
A) EDDM
|
||||
B) 2307150600 C) 2307151800
|
||||
E) AERODROME CONTROL TOWER HOURS OF SERVICE
|
||||
0600-1800 DUE TO MAINTENANCE
|
||||
NNNN
|
||||
`
|
||||
|
||||
parsedMessage, err := ParseHeader(message)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(parsedMessage.StartIndicator).To(Equal("ZCZC"))
|
||||
Expect(parsedMessage.MessageID).To(Equal("NOTAM1234"))
|
||||
Expect(parsedMessage.DateTime).To(Equal("230715"))
|
||||
Expect(parsedMessage.PriorityIndicator).To(Equal("GG"))
|
||||
Expect(parsedMessage.PrimaryAddress).To(Equal("EDDNZEZN"))
|
||||
Expect(parsedMessage.SecondaryAddresses).To(Equal([]string{"GG EDDNYNYX"}))
|
||||
Expect(parsedMessage.Originator).To(Equal("BERLINTWR"))
|
||||
Expect(parsedMessage.OriginatorDateTime).To(Equal("230714"))
|
||||
// fmt.Print(parsedMessage.BodyAndFooter)
|
||||
Expect(parsedMessage.BodyAndFooter).To(Equal(`
|
||||
Q) EDMM/QOATT/IV/BO/A/000/999/4814N01120E005
|
||||
A) EDDM
|
||||
B) 2307150600 C) 2307151800
|
||||
E) AERODROME CONTROL TOWER HOURS OF SERVICE
|
||||
0600-1800 DUE TO MAINTENANCE
|
||||
NNNN
|
||||
`))
|
||||
})
|
||||
})
|
||||
Describe("ParseBody", func() {
|
||||
|
||||
Context("with ARR body (ARR-CES5470-ZBTJ-ZSHC1614)", func() {
|
||||
@@ -187,19 +149,19 @@ NNNN
|
||||
|
||||
Context("with DEP body", func() {
|
||||
parser := NewBodyParser()
|
||||
It("should parse the body (DEP-AB123-SSR1234-KJFK-1500-KLAX) correctly", func() {
|
||||
body := "(DEP-AB123-SSR1234-KJFK-1500-KLAX)"
|
||||
It("should parse the body (DEP-CYZ9017/A5633-ZBTJ1638-ZSPD) correctly", func() {
|
||||
body := "(DEP-CYZ9017/A5633-ZBTJ1638-ZSPD)"
|
||||
parsedBody, err := parser.Parse(body)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(parsedBody).ToNot(BeNil())
|
||||
Expect(parsedBody).To(BeAssignableToTypeOf(&domain.DEP{}))
|
||||
depMessage := parsedBody.(*domain.DEP)
|
||||
Expect(depMessage.Category).To(Equal("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"))
|
||||
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"))
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user