From 99a727592328da60fcb5f4fccc8f350086ea4a03 Mon Sep 17 00:00:00 2001 From: Motalleb Fallahnezhad Date: Wed, 5 Jun 2024 02:36:41 +0330 Subject: [PATCH] feat: added config validation --- abstraction/validatable.go | 8 +++ cmd/root.go | 2 +- config/config.go | 64 ++++++------------- config/validators.go | 122 +++++++++++++++++++++++++++++++++++++ enums/log_level.go | 15 ++++- 5 files changed, 164 insertions(+), 47 deletions(-) create mode 100644 abstraction/validatable.go create mode 100644 config/validators.go diff --git a/abstraction/validatable.go b/abstraction/validatable.go new file mode 100644 index 0000000..4795f72 --- /dev/null +++ b/abstraction/validatable.go @@ -0,0 +1,8 @@ +package abstraction + +type ( + ValidatableStr string + Validatable interface { + Validate() error + } +) diff --git a/cmd/root.go b/cmd/root.go index 2e5f9ef..5c6bd7d 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -118,7 +118,7 @@ func initConfig() { "Cannot unmarshal the config file: %s", ) panicOnErr( - CFG.Initialize(), + CFG.Validate(), "Failed to initialize config file: %s", ) } diff --git a/config/config.go b/config/config.go index 6a95a37..109821a 100644 --- a/config/config.go +++ b/config/config.go @@ -20,23 +20,23 @@ type ( } JobConfig struct { - Name string `mapstructure:"name" json:"name,omitempty"` - Description string `mapstructure:"description" json:"description,omitempty"` - Enabled bool `mapstructure:"enabled" json:"enabled,omitempty"` - Tasks []Task `mapstructure:"tasks" json:"tasks,omitempty"` - Scheduler JobScheduler `mapstructure:"scheduler" json:"scheduler"` - Retries int `mapstructure:"retries" json:"retries,omitempty"` - RetryDelay time.Duration `mapstructure:"retry-delay" json:"retry_delay,omitempty"` - Timeout time.Duration `mapstructure:"timeout" json:"timeout,omitempty"` - Hooks JobHooks `mapstructure:"hooks" json:"hooks,omitempty"` - Env EnvVariables `mapstructure:"env" json:"env,omitempty"` - Metadata JobMetadata `mapstructure:"metadata" json:"metadata,omitempty"` + Name string `mapstructure:"name" json:"name,omitempty"` + Description string `mapstructure:"description" json:"description,omitempty"` + Enabled bool `mapstructure:"enabled" json:"enabled,omitempty"` + Tasks []Task `mapstructure:"tasks" json:"tasks,omitempty"` + Schedulers []JobScheduler `mapstructure:"schedulers" json:"schedulers"` + Retries uint `mapstructure:"retries" json:"retries,omitempty"` + RetryDelay time.Duration `mapstructure:"retry-delay" json:"retry_delay,omitempty"` + Timeout time.Duration `mapstructure:"timeout" json:"timeout,omitempty"` + Hooks JobHooks `mapstructure:"hooks" json:"hooks,omitempty"` + Env EnvVariables `mapstructure:"env" json:"env,omitempty"` + Metadata JobMetadata `mapstructure:"metadata" json:"metadata,omitempty"` } JobScheduler struct { Cron string `mapstructure:"cron" json:"cron,omitempty"` Interval time.Duration `mapstructure:"interval" json:"interval,omitempty"` - At time.Time `mapstructure:"at" json:"at,omitempty"` + At *time.Time `mapstructure:"at" json:"at,omitempty"` } JobHooks struct { @@ -45,38 +45,12 @@ type ( } Task struct { - Post string `mapstructure:"get" json:"post,omitempty"` - Get string `mapstructure:"post" json:"get,omitempty"` - Command string `mapstructure:"command" json:"command,omitempty"` - Args []string `mapstructure:"args" json:"args,omitempty"` - Headers map[string]string `mapstructure:"headers" json:"headers,omitempty"` - Data map[string]any `mapstructure:"data" json:"data,omitempty"` + Post string `mapstructure:"get" json:"post,omitempty"` + Get string `mapstructure:"post" json:"get,omitempty"` + Command string `mapstructure:"command" json:"command,omitempty"` + Args []string `mapstructure:"args" json:"args,omitempty"` + WorkingDirectory string `mapstructure:"working_directory" json:"working_directory,omitempty"` + Headers map[string]string `mapstructure:"headers" json:"headers,omitempty"` + Data map[string]any `mapstructure:"data" json:"data,omitempty"` } ) - -func (t *Task) Initialize() error { - return nil -} - -func (h *JobHooks) Initialize() error { - return nil -} - -func (s *JobScheduler) Initialize() error { - return nil -} - -func (j *JobConfig) Initialize() error { - return nil -} - -func (cfg *Config) Initialize() error { - if cfg.LogFormat == "" { - cfg.LogFormat = enums.AnsiLogger - } - if err := cfg.LogFormat.Validate(); err != nil { - return err - } - - return nil -} diff --git a/config/validators.go b/config/validators.go new file mode 100644 index 0000000..8749f7f --- /dev/null +++ b/config/validators.go @@ -0,0 +1,122 @@ +package config + +import ( + "fmt" + "time" + + "github.com/robfig/cron/v3" +) + +var cronParser = cron.NewParser(cron.SecondOptional) + +func (cfg *Config) Validate() error { + if err := cfg.LogFormat.Validate(); err != nil { + return err + } + if err := cfg.LogLevel.Validate(); err != nil { + return err + } + for _, job := range cfg.Jobs { + if err := job.Validate(); err != nil { + return err + } + } + return nil +} + +func (c *JobConfig) Validate() error { + if c.Enabled == false { + return nil + } + if c.Timeout < 0 { + return fmt.Errorf( + "timeout for jobs cannot be negative received `%s` for `%s`", + c.Timeout, + c.Name, + ) + } + + if c.RetryDelay < 0 { + return fmt.Errorf( + "retry delay for jobs cannot be negative received `%s` for `%s`", + c.RetryDelay, + c.Name, + ) + } + for _, s := range c.Schedulers { + if err := s.Validate(); err != nil { + return err + } + } + for _, t := range c.Tasks { + if err := t.Validate(); err != nil { + return err + } + } + for _, t := range c.Hooks.Done { + if err := t.Validate(); err != nil { + return err + } + } + for _, t := range c.Hooks.Failed { + if err := t.Validate(); err != nil { + return err + } + } + return nil +} + +func (c *Task) Validate() error { + actions := []bool{ + c.Get != "", + c.Command != "", + c.Post != "", + } + activeActions := 0 + for _, t := range actions { + if t { + activeActions++ + } + } + if activeActions != 1 { + return fmt.Errorf( + "a single task should have one of (get,post,command) fields, received:(command: `%s`, get: `%s`, post: `%s`)", + c.Command, + c.Get, + c.Post, + ) + } + return nil +} + +func (s *JobScheduler) Validate() error { + if s.At != nil { + if s.At.Before(time.Now()) { + fmt.Println("you've set the time in the scheduler that is before now, received:", s.At, "Given time will be ignored") + } + } else if s.Interval < 0 { + return fmt.Errorf("received a negative time in interval: `%v`", s.Interval) + } else if _, err := cronParser.Parse(s.Cron); err != nil { + return err + } + schedules := []bool{ + s.At != nil, + s.Interval != 0, + s.Cron != "", + } + activeSchedules := 0 + for _, t := range schedules { + if t { + activeSchedules++ + } + } + if activeSchedules != 1 { + return fmt.Errorf( + "a single scheduler must have one of (at,interval,cron) field, received:(cron: `%s`, interval: `%s`, at: `%s`)", + s.Cron, + s.Interval, + s.At, + ) + } + return nil +} diff --git a/enums/log_level.go b/enums/log_level.go index 6d8a26f..26c5c8b 100644 --- a/enums/log_level.go +++ b/enums/log_level.go @@ -1,6 +1,10 @@ package enums -import "github.com/sirupsen/logrus" +import ( + "fmt" + + "github.com/sirupsen/logrus" +) type LogLevel string @@ -13,6 +17,15 @@ const ( PanicLevel = LogLevel("panic") ) +func (l LogLevel) Validate() error { + switch l { + case TraceLevel, DebugLevel, InfoLevel, WarnLevel, FatalLevel, PanicLevel: + return nil + default: + return fmt.Errorf("LogLevel must be one of (trace,debug,info,warn,fatal,panic) but received `%s`", l) + } +} + func (l LogLevel) ToLogrusLevel() (logrus.Level, error) { return logrus.ParseLevel(string(l)) }