diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..91cb520 --- /dev/null +++ b/Makefile @@ -0,0 +1,84 @@ +# Variables +APP_NAME = tele-proc +GO_FILES = $(shell find . -name '*.go' -type f) +CONFIG_DIR = configs +BUILD_DIR = build +MAIN_RECEIVER = ./cmd/receiver/main.go + +# Default target +.PHONY: all +all: build + +# Build the receiver application +.PHONY: build +build: build-receiver + +.PHONY: build-receiver +build-receiver: + @echo "Building receiver..." + @go build -o $(BUILD_DIR)/receiver $(MAIN_RECEIVER) + +# Run the receiver application with different configurations +.PHONY: run +run: run-dev + +.PHONY: run-dev +run-dev: + @echo "Running receiver in development mode..." + @GO_ENV=development $(BUILD_DIR)/receiver & + +.PHONY: run-prod +run-prod: + @echo "Running receiver in production mode..." + @GO_ENV=production $(BUILD_DIR)/receiver & + +.PHONY: run-test +run-test: + @echo "Running receiver in test mode..." + @GO_ENV=test $(BUILD_DIR)/receiver & + +# Test the application +.PHONY: test +test: + @echo "Running tests..." + @go test ./... + +# Clean build artifacts +.PHONY: clean +clean: + @echo "Cleaning build artifacts..." + @rm -rf $(BUILD_DIR) + +# Format the code +.PHONY: fmt +fmt: + @echo "Formatting code..." + @go fmt ./... + +# Install dependencies +.PHONY: deps +deps: + @echo "Installing dependencies..." + @go mod tidy + +# Lint the code +.PHONY: lint +lint: + @echo "Linting code..." + @golangci-lint run + +# Help +.PHONY: help +help: + @echo "Makefile usage:" + @echo " make build - Build the application" + @echo " make run - Run the receiver in development mode" + @echo " make run-dev - Run the receiver in development mode" + @echo " make run-prod - Run the receiver in production mode" + @echo " make run-test - Run the receiver in test mode" + @echo " make test - Run tests" + @echo " make clean - Clean build artifacts" + @echo " make fmt - Format the code" + @echo " make deps - Install dependencies" + @echo " make lint - Lint the code" + @echo " make help - Show this help message" diff --git a/cmd/main/main.go b/cmd/main/main.go new file mode 100644 index 0000000..056db09 --- /dev/null +++ b/cmd/main/main.go @@ -0,0 +1,26 @@ +package main + +import ( + "caatsm/internal/config" + "caatsm/pkg/utils" + + "github.com/sirupsen/logrus" +) + +func main() { + cfg, err := config.LoadConfig() + if err != nil { + utils.Logger.WithError(err).Fatal("Error loading config") + } + + if err := config.ValidateConfig(cfg); err != nil { + utils.Logger.WithError(err).Fatal("Config validation error") + } + + utils.Logger.Info("Loaded configuration successfully") + + // Example usage + utils.Logger.WithFields(logrus.Fields{ + "url": cfg.Nats.URL, + }).Info("NATS configuration") +} diff --git a/internal/config/config.go b/internal/config/config.go new file mode 100644 index 0000000..feabf80 --- /dev/null +++ b/internal/config/config.go @@ -0,0 +1,164 @@ +package config + +import ( + "caatsm/pkg/utils" + "fmt" + "os" + "regexp" + "strings" + + "github.com/spf13/viper" + // Adjust this import based on your project structure +) + +var MyConfig *Config + +type Config struct { + Nats NatsConfig + Subscription SubscriptionConfig + Timeouts TimeoutsConfig + Body []BodyConfig +} + +type NatsConfig struct { + Client string + URL string + Cluster string +} + +type SubscriptionConfig struct { + Topic string + QueueGroup string `mapstructure:"queue_group"` +} + +type TimeoutsConfig struct { + ServerTimeout string `mapstructure:"server_timeout"` + ReconnectWait string `mapstructure:"reconnect_wait"` + CloseTimeout string `mapstructure:"close_timeout"` + AckWaitTimeout string `mapstructure:"ack_wait_timeout"` +} + +type BodyConfig struct { + Name string + Patterns []PatternConfig +} + +type PatternConfig struct { + Pattern string + Comments string + Expression *regexp.Regexp +} + +func SetMyConfig(cfg *Config) { + MyConfig = cfg +} + +func GetMyConfig() *Config { + if MyConfig == nil { + cfg, err := LoadConfig() + if err != nil { + utils.Logger.Fatalf("error loading config: %v", err) + } + MyConfig = cfg + } + return MyConfig +} + +// LoadConfig loads the configuration from a file +func LoadConfig() (*Config, error) { + log := utils.Logger + env := os.Getenv("GO_ENV") + if env == "" { + env = "dev" + } + log.Infof("Environment: %s", env) + + viper.SetConfigType("toml") + viper.SetConfigName("config." + env) + viper.AddConfigPath("configs") + viper.SetEnvPrefix("tele") + viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) + + if err := viper.ReadInConfig(); err != nil { + errMsg := fmt.Sprintf("error reading config file for environment '%s': %v", env, err) + log.Error(errMsg) + return nil, fmt.Errorf(errMsg) + } + + log.Debug("Config file read successfully") + log.Debugf("Config keys: %v", viper.AllKeys()) + + var config Config + if err := viper.Unmarshal(&config); err != nil { + errMsg := fmt.Sprintf("unable to decode config into struct for environment '%s': %v", env, err) + log.Error(errMsg) + return nil, fmt.Errorf(errMsg) + } + + log.Debugf("Config loaded before regex compilation: %+v", config) + + // Compile regex patterns + for i := range config.Body { + for j := range config.Body[i].Patterns { + name := config.Body[i].Name + pattern := config.Body[i].Patterns[j].Pattern + expr, err := regexp.Compile(pattern) + if err != nil { + errMsg := fmt.Sprintf("error compiling regex for body '%s', pattern '%s': %v", name, pattern, err) + log.Error(errMsg) + return nil, fmt.Errorf(errMsg) + } + config.Body[i].Patterns[j].Expression = expr + } + } + + log.Debugf("Final config after regex compilation: %+v", config) + return &config, nil +} + +// ValidateConfig validates the loaded configuration +func ValidateConfig(cfg *Config) error { + log := utils.Logger + + if cfg.Nats.Client == "" { + err := "nats client is required" + log.Error(err) + return fmt.Errorf(err) + } + if cfg.Nats.URL == "" { + err := "nats URL is required" + log.Error(err) + return fmt.Errorf(err) + } + if cfg.Subscription.Topic == "" { + err := "subscription topic is required" + log.Error(err) + return fmt.Errorf(err) + } + if len(cfg.Body) == 0 { + err := "at least one body configuration is required" + log.Error(err) + return fmt.Errorf(err) + } + for _, body := range cfg.Body { + if body.Name == "" { + err := "body name is required" + log.Error(err) + return fmt.Errorf(err) + } + for _, pattern := range body.Patterns { + if pattern.Pattern == "" { + err := fmt.Sprintf("pattern is required for body '%s'", body.Name) + log.Error(err) + return fmt.Errorf(err) + } + if pattern.Expression == nil { + err := fmt.Sprintf("compiled expression is missing for pattern '%s' in body '%s'", pattern.Pattern, body.Name) + log.Error(err) + return fmt.Errorf(err) + } + } + } + log.Info("config validation passed") + return nil +} diff --git a/internal/config/config_test.go b/internal/config/config_test.go new file mode 100644 index 0000000..6fed31c --- /dev/null +++ b/internal/config/config_test.go @@ -0,0 +1,128 @@ +package config + +import ( + "os" + "path/filepath" + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +func TestConfig(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Config Suite") +} + +var _ = Describe("Config", func() { + var originalEnv string + + BeforeEach(func() { + // Save the original GO_ENV value + originalEnv = os.Getenv("GO_ENV") + }) + + AfterEach(func() { + // Restore the original GO_ENV value + os.Setenv("GO_ENV", originalEnv) + }) + + // Helper function to create temporary TOML files in the "configs" directory + createTempConfigFile := func(env, content string) string { + dir := "configs" + err := os.MkdirAll(dir, 0755) + Expect(err).NotTo(HaveOccurred(), "failed to create config directory") + + filename := filepath.Join(dir, "config."+env+".toml") + tmpfile, err := os.Create(filename) + Expect(err).NotTo(HaveOccurred(), "failed to create temp config file") + _, err = tmpfile.Write([]byte(content)) + Expect(err).NotTo(HaveOccurred(), "failed to write to temp config file") + err = tmpfile.Close() + Expect(err).NotTo(HaveOccurred(), "failed to close temp config file") + return filename + } + + Context("Loading and validating a valid TOML file", func() { + It("should load and validate the configuration correctly", func() { + content := ` +[nats] +client = "test-client" +url = "nats://localhost:4222" +cluster = "test-cluster" + +[subscription] +topic = "example-topic" +queue_group = "example-group" + +[timeouts] +server_timeout = "30s" +reconnect_wait = "10s" +close_timeout = "10s" +ack_wait_timeout = "5s" + +[[body]] +name = "FPL" +[[body.patterns]] +pattern = "^\\((?P[A-Z]{3})\\-(?P[A-Z]+\\d+)\\-(?P[A-Z]{2})(?:.*\\s*)?\\-(?P[A-Z]+\\d+/?[A-Z]?)\\s*\\-(?P.*)\\s*\\-(?P[A-Z]{4})(?P\\d{4})\\s*\\-(?P[A-Z]+\\d+)(?P[A-Z0-9]+)\\s(?P.*)\\s*\\-(?P[A-Z]{4})(?P\\d{4})\\s(?P[A-Z]{4})\\s*\\-(?PPBN\\/[A-Z0-9]+)\\s(?P