package config import ( "fmt" "os" "strings" ) type EffectiveConfigErrorKind string const ( EffectiveConfigErrorResolvePath EffectiveConfigErrorKind = "resolve_path" EffectiveConfigErrorLoadFile EffectiveConfigErrorKind = "load_file" EffectiveConfigErrorApplyFile EffectiveConfigErrorKind = "apply_file" EffectiveConfigErrorApplyEnv EffectiveConfigErrorKind = "apply_env" ) type EffectiveConfigError struct { Kind EffectiveConfigErrorKind Err error } func (e *EffectiveConfigError) Error() string { if e == nil || e.Err == nil { return "" } return e.Err.Error() } func (e *EffectiveConfigError) Unwrap() error { if e == nil { return nil } return e.Err } type EffectiveConfig struct { Config Config ConfigPath string ConfigSource string ConfigVersion *int } func ResolveConfigPath(cliConfigPath string, cliConfigPathSet bool) (path string, source string, err error) { return resolveConfigPathWithLookup(cliConfigPath, cliConfigPathSet, os.LookupEnv, os.Stat, DefaultConfigSearchPaths) } func LoadEffectiveConfig(cliConfigPath string, cliConfigPathSet bool) (EffectiveConfig, error) { return loadEffectiveConfigWithLookup(cliConfigPath, cliConfigPathSet, os.LookupEnv, os.Stat, DefaultConfigSearchPaths) } func loadEffectiveConfigWithLookup(cliConfigPath string, cliConfigPathSet bool, lookup func(string) (string, bool), statPath func(string) (os.FileInfo, error), defaultSearchPaths []string) (EffectiveConfig, error) { configPath, configSource, err := resolveConfigPathWithLookup(cliConfigPath, cliConfigPathSet, lookup, statPath, defaultSearchPaths) if err != nil { return EffectiveConfig{}, &EffectiveConfigError{Kind: EffectiveConfigErrorResolvePath, Err: err} } cfg := Default() var configVersion *int if configPath != "" { fileCfg, fileErr := LoadFileConfig(configPath) if fileErr != nil { return EffectiveConfig{}, &EffectiveConfigError{Kind: EffectiveConfigErrorLoadFile, Err: fileErr} } if applyErr := cfg.ApplyFileConfig(fileCfg); applyErr != nil { return EffectiveConfig{}, &EffectiveConfigError{Kind: EffectiveConfigErrorApplyFile, Err: applyErr} } configVersion = &fileCfg.Version } if applyEnvErr := cfg.applyEnvOverrides(lookup); applyEnvErr != nil { return EffectiveConfig{}, &EffectiveConfigError{Kind: EffectiveConfigErrorApplyEnv, Err: applyEnvErr} } return EffectiveConfig{ Config: cfg, ConfigPath: configPath, ConfigSource: configSource, ConfigVersion: configVersion, }, nil } func resolveConfigPathWithLookup(cliConfigPath string, cliConfigPathSet bool, lookup func(string) (string, bool), statPath func(string) (os.FileInfo, error), defaultSearchPaths []string) (path string, source string, err error) { if cliConfigPathSet { path = strings.TrimSpace(cliConfigPath) if path == "" { return "", "", fmt.Errorf("--config requires a non-empty path") } if _, statErr := statPath(path); statErr != nil { if os.IsNotExist(statErr) { return "", "", fmt.Errorf("config file not found: %s", path) } return "", "", fmt.Errorf("cannot access config file %s: %w", path, statErr) } return path, "flag", nil } if raw, ok := lookup("AUDITA_CONFIG"); ok { path = strings.TrimSpace(raw) if path == "" { return "", "", fmt.Errorf("AUDITA_CONFIG must not be empty") } if _, statErr := statPath(path); statErr != nil { if os.IsNotExist(statErr) { return "", "", fmt.Errorf("config file not found: %s", path) } return "", "", fmt.Errorf("cannot access config file %s: %w", path, statErr) } return path, "env", nil } for _, defaultPath := range defaultSearchPaths { if _, statErr := statPath(defaultPath); statErr == nil { return defaultPath, "default", nil } else if !os.IsNotExist(statErr) { return "", "", fmt.Errorf("cannot access config file %s: %w", defaultPath, statErr) } } return "", "", nil }