package config import ( "encoding/json" "fmt" "log/slog" "reflect" "strings" "github.com/spf13/viper" "go.yaml.in/yaml/v3" ) const EnvPrefix string = "JOBSEARCH" const EtcConfigPath string = "/etc/job-search" // Each field's cfg tag must match the section's JSCK* constant. type JobSearchConfig struct { Logging JobSearchConfigLogging `cfg:"logging"` Sources JobSearchConfigSources `cfg:"sources"` } // Default configuration, taken from the default struct tags func LoadDefaultConfig() { var conf JobSearchConfig if err := applyDefaults(&conf); err != nil { panic(fmt.Sprintf("config: %v", err)) } LoadToViper(conf) } func (c JobSearchConfig) Map() map[string]any { return mapOf(c) } func (c JobSearchConfig) Flatten() map[string]any { return flattenOf("", c) } func (c JobSearchConfig) TypeOf(key string) (reflect.Kind, error) { return typeOf(c, key) } func (c JobSearchConfig) ValueOf(key string) (any, error) { return valueOf(c, key) } func (c *JobSearchConfig) SetValue(key string, value any) error { return setValue(c, key, value) } // Utility method for loading a configuration from Viper func LoadFromViper(conf *JobSearchConfig) { keys := viper.AllKeys() for idx := range keys { key := keys[idx] keyType, _ := conf.TypeOf(key) if keyType == reflect.Invalid { continue } var err error switch keyType { case reflect.String: err = conf.SetValue(key, viper.GetString(key)) case reflect.Int: err = conf.SetValue(key, viper.GetInt(key)) case reflect.Bool: err = conf.SetValue(key, viper.GetBool(key)) default: slog.Error(fmt.Sprintf("unknown key type: %s", keyType)) } if err != nil { slog.Warn("ignoring invalid config value", "key", key, "err", err) } } } // Utility method for loading a configuration into Viper as defaults func LoadToViper(conf JobSearchConfig) { flatMap := conf.Flatten() for key := range flatMap { viper.SetDefault(key, flatMap[key]) } } // Converts configuration to a string in flat dotted format func (conf JobSearchConfig) String() string { var sb strings.Builder for k, v := range conf.Flatten() { typ, _ := conf.TypeOf(k) sb.WriteString(fmt.Sprint(k, " = ", v, " <", typ, ">\n")) } return sb.String() } // Converts configuration to JSON func (conf JobSearchConfig) JSON() string { confMap := conf.Map() jbytes, _ := json.MarshalIndent(confMap, "", " ") return string(jbytes) } // Converts configuration to YAML func (conf JobSearchConfig) YAML() string { confMap := conf.Map() ybytes, _ := yaml.Marshal(confMap) return string(ybytes) }