v0.12.2 config validation updated, distributed to their own pacakges and plugins
This commit is contained in:
@@ -113,7 +113,18 @@ func defaults() *Config {
|
||||
Pipelines: []PipelineConfig{
|
||||
{
|
||||
Name: "default_pipeline",
|
||||
Flow: &FlowConfig{},
|
||||
Flow: &FlowConfig{
|
||||
RateLimit: &RateLimitConfig{
|
||||
Rate: 5,
|
||||
Burst: 10,
|
||||
Policy: "drop",
|
||||
MaxEntrySizeBytes: 65536,
|
||||
},
|
||||
Format: &FormatConfig{
|
||||
Type: "json",
|
||||
SanitizerPolicy: "json",
|
||||
},
|
||||
},
|
||||
PluginSources: []PluginSourceConfig{
|
||||
{
|
||||
ID: "default_source",
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
lconfig "github.com/lixenwraith/config"
|
||||
)
|
||||
|
||||
// ValidateConfig validates top-level structure only
|
||||
// Value range validation is delegated to component constructors
|
||||
func ValidateConfig(cfg *Config) error {
|
||||
if cfg == nil {
|
||||
return fmt.Errorf("config is nil")
|
||||
}
|
||||
|
||||
if len(cfg.Pipelines) == 0 {
|
||||
return fmt.Errorf("no pipelines configured")
|
||||
}
|
||||
|
||||
if err := validateLogConfig(cfg.Logging); err != nil {
|
||||
return fmt.Errorf("logging: %w", err)
|
||||
}
|
||||
|
||||
for i, p := range cfg.Pipelines {
|
||||
if err := lconfig.NonEmpty(p.Name); err != nil {
|
||||
return fmt.Errorf("pipeline[%d].name: %w", i, err)
|
||||
}
|
||||
if len(p.PluginSources) == 0 {
|
||||
return fmt.Errorf("pipeline[%d]: no sources defined", i)
|
||||
}
|
||||
if len(p.PluginSinks) == 0 {
|
||||
return fmt.Errorf("pipeline[%d]: no sinks defined", i)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateLogConfig validates application logging settings
|
||||
func validateLogConfig(cfg *LogConfig) error {
|
||||
if cfg == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
validateOutput := lconfig.OneOf("file", "stdout", "stderr", "split", "all", "none")
|
||||
if err := validateOutput(cfg.Output); err != nil {
|
||||
return fmt.Errorf("output: %w", err)
|
||||
}
|
||||
|
||||
validateLevel := lconfig.OneOf("debug", "info", "warn", "error")
|
||||
if err := validateLevel(cfg.Level); err != nil {
|
||||
return fmt.Errorf("level: %w", err)
|
||||
}
|
||||
|
||||
if cfg.Console != nil {
|
||||
validateTarget := lconfig.OneOf("stdout", "stderr", "split")
|
||||
if err := validateTarget(cfg.Console.Target); err != nil {
|
||||
return fmt.Errorf("console.target: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,234 +0,0 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
lconfig "github.com/lixenwraith/config"
|
||||
)
|
||||
|
||||
// ValidateConfig is the centralized validator for the entire configuration structure
|
||||
func ValidateConfig(cfg *Config) error {
|
||||
if cfg == nil {
|
||||
return fmt.Errorf("config is nil")
|
||||
}
|
||||
|
||||
if len(cfg.Pipelines) == 0 {
|
||||
return fmt.Errorf("no pipelines configured")
|
||||
}
|
||||
|
||||
if err := validateLogConfig(cfg.Logging); err != nil {
|
||||
return fmt.Errorf("logging config: %w", err)
|
||||
}
|
||||
|
||||
// // Track used ports across all pipelines
|
||||
// allPorts := make(map[int64]string)
|
||||
// pipelineNames := make(map[string]bool)
|
||||
|
||||
// for i, pipeline := range cfg.Pipelines {
|
||||
// if err := validatePipeline(i, &pipeline, pipelineNames, allPorts); err != nil {
|
||||
// return err
|
||||
// }
|
||||
// }
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateLogConfig validates the application's own logging settings
|
||||
func validateLogConfig(cfg *LogConfig) error {
|
||||
validOutputs := map[string]bool{
|
||||
"file": true, "stdout": true, "stderr": true,
|
||||
"split": true, "all": true, "none": true,
|
||||
}
|
||||
if !validOutputs[cfg.Output] {
|
||||
return fmt.Errorf("invalid log output mode: %s", cfg.Output)
|
||||
}
|
||||
|
||||
validLevels := map[string]bool{
|
||||
"debug": true, "info": true, "warn": true, "error": true,
|
||||
}
|
||||
if !validLevels[cfg.Level] {
|
||||
return fmt.Errorf("invalid log level: %s", cfg.Level)
|
||||
}
|
||||
|
||||
if cfg.Console != nil {
|
||||
validTargets := map[string]bool{
|
||||
"stdout": true, "stderr": true, "split": true,
|
||||
}
|
||||
if !validTargets[cfg.Console.Target] {
|
||||
return fmt.Errorf("invalid console target: %s", cfg.Console.Target)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateRateLimit validates the pipeline-level rate limit settings
|
||||
func validateRateLimit(pipelineName string, cfg *RateLimitConfig) error {
|
||||
if cfg == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if cfg.Rate < 0 {
|
||||
return fmt.Errorf("pipeline '%s': rate limit rate cannot be negative", pipelineName)
|
||||
}
|
||||
|
||||
if cfg.Burst < 0 {
|
||||
return fmt.Errorf("pipeline '%s': rate limit burst cannot be negative", pipelineName)
|
||||
}
|
||||
|
||||
if cfg.MaxEntrySizeBytes < 0 {
|
||||
return fmt.Errorf("pipeline '%s': max entry size bytes cannot be negative", pipelineName)
|
||||
}
|
||||
|
||||
// Validate policy
|
||||
switch strings.ToLower(cfg.Policy) {
|
||||
case "", "pass", "drop":
|
||||
// Valid policies
|
||||
default:
|
||||
return fmt.Errorf("pipeline '%s': invalid rate limit policy '%s' (must be 'pass' or 'drop')",
|
||||
pipelineName, cfg.Policy)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateFilter validates a single filter's configuration
|
||||
func validateFilter(pipelineName string, filterIndex int, cfg *FilterConfig) error {
|
||||
// Validate filter type
|
||||
switch cfg.Type {
|
||||
case FilterTypeInclude, FilterTypeExclude, "":
|
||||
// Valid types
|
||||
default:
|
||||
return fmt.Errorf("pipeline '%s' filter[%d]: invalid type '%s' (must be 'include' or 'exclude')",
|
||||
pipelineName, filterIndex, cfg.Type)
|
||||
}
|
||||
|
||||
// Validate filter logic
|
||||
switch cfg.Logic {
|
||||
case FilterLogicOr, FilterLogicAnd, "":
|
||||
// Valid logic
|
||||
default:
|
||||
return fmt.Errorf("pipeline '%s' filter[%d]: invalid logic '%s' (must be 'or' or 'and')",
|
||||
pipelineName, filterIndex, cfg.Logic)
|
||||
}
|
||||
|
||||
// Empty patterns is valid - passes everything
|
||||
if len(cfg.Patterns) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate regex patterns
|
||||
for i, pattern := range cfg.Patterns {
|
||||
if _, err := regexp.Compile(pattern); err != nil {
|
||||
return fmt.Errorf("pipeline '%s' filter[%d] pattern[%d] '%s': invalid regex: %w",
|
||||
pipelineName, filterIndex, i, pattern, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateFileSource validates the settings for a directory source
|
||||
func validateFileSource(pipelineName string, index int, opts *FileSourceOptions) error {
|
||||
if err := lconfig.NonEmpty(opts.Directory); err != nil {
|
||||
return fmt.Errorf("pipeline '%s' source[%d]: directory requires 'path'", pipelineName, index)
|
||||
} else {
|
||||
absPath, err := filepath.Abs(opts.Directory)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid path %s: %w", opts.Directory, err)
|
||||
}
|
||||
opts.Directory = absPath
|
||||
}
|
||||
|
||||
// Check for directory traversal
|
||||
if strings.Contains(opts.Directory, "..") {
|
||||
return fmt.Errorf("pipeline '%s' source[%d]: path contains directory traversal", pipelineName, index)
|
||||
}
|
||||
|
||||
// Validate pattern if provided
|
||||
if opts.Pattern != "" {
|
||||
if strings.Count(opts.Pattern, "*") == 0 && strings.Count(opts.Pattern, "?") == 0 {
|
||||
// If no wildcards, ensure valid filename
|
||||
if filepath.Base(opts.Pattern) != opts.Pattern {
|
||||
return fmt.Errorf("pipeline '%s' source[%d]: pattern contains path separators", pipelineName, index)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
opts.Pattern = "*"
|
||||
}
|
||||
|
||||
// Validate check interval
|
||||
if opts.CheckIntervalMS < 10 {
|
||||
return fmt.Errorf("pipeline '%s' source[%d]: check_interval_ms must be at least 10ms", pipelineName, index)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateConsoleSource validates the settings for a console source
|
||||
func validateConsoleSource(pipelineName string, index int, opts *ConsoleSourceOptions) error {
|
||||
if opts.BufferSize < 0 {
|
||||
return fmt.Errorf("pipeline '%s' source[%d]: buffer_size must be positive", pipelineName, index)
|
||||
} else if opts.BufferSize == 0 {
|
||||
opts.BufferSize = 1000
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateConsoleSink validates the settings for a console sink
|
||||
func validateConsoleSink(pipelineName string, index int, opts *ConsoleSinkOptions) error {
|
||||
if opts.BufferSize < 1 {
|
||||
return fmt.Errorf("pipeline '%s' sink[%d]: buffer_size must be positive", pipelineName, index)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateFileSink validates the settings for a file sink
|
||||
func validateFileSink(pipelineName string, index int, opts *FileSinkOptions) error {
|
||||
if err := lconfig.NonEmpty(opts.Directory); err != nil {
|
||||
return fmt.Errorf("pipeline '%s' sink[%d]: file requires 'directory'", pipelineName, index)
|
||||
}
|
||||
|
||||
if err := lconfig.NonEmpty(opts.Name); err != nil {
|
||||
return fmt.Errorf("pipeline '%s' sink[%d]: file requires 'name'", pipelineName, index)
|
||||
}
|
||||
|
||||
if opts.BufferSize <= 0 {
|
||||
return fmt.Errorf("pipeline '%s' sink[%d]: max_size_mb must be positive", pipelineName, index)
|
||||
}
|
||||
|
||||
// Validate sizes
|
||||
if opts.MaxSizeMB < 0 {
|
||||
return fmt.Errorf("pipeline '%s' sink[%d]: max_size_mb must be positive", pipelineName, index)
|
||||
}
|
||||
|
||||
if opts.MaxTotalSizeMB <= 0 {
|
||||
return fmt.Errorf("pipeline '%s' sink[%d]: max_total_size_mb cannot be negative", pipelineName, index)
|
||||
}
|
||||
|
||||
if opts.MinDiskFreeMB < 0 {
|
||||
return fmt.Errorf("pipeline '%s' sink[%d]: min_disk_free_mb must be positive", pipelineName, index)
|
||||
}
|
||||
|
||||
if opts.RetentionHours <= 0 {
|
||||
return fmt.Errorf("pipeline '%s' sink[%d]: retention_hours cannot be negative", pipelineName, index)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateHeartbeat validates nested HeartbeatConfig settings
|
||||
func validateHeartbeat(pipelineName, location string, hb *HeartbeatConfig) error {
|
||||
if !hb.Enabled {
|
||||
return nil // Skip validation if disabled
|
||||
}
|
||||
|
||||
if hb.IntervalMS < 1000 { // At least 1 second
|
||||
return fmt.Errorf("pipeline '%s' %s: heartbeat interval must be at least 1000ms", pipelineName, location)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user