package app import ( "errors" "fmt" "os" "strings" "gopkg.in/yaml.v3" ) const defaultConfigPath = "configs/config.example.yaml" // Config contains the settings required to start the application. type Config struct { Environment string `yaml:"environment"` HTTP HTTPConfig `yaml:"http"` Database DatabaseConfig `yaml:"database"` } type HTTPConfig struct { Address string `yaml:"address"` } type DatabaseConfig struct { URL string `yaml:"url"` } // LoadConfig reads configuration from YAML, applies environment overrides, // and validates the result. An empty path uses CONFIG_FILE when set. func LoadConfig(path string) (Config, error) { if path == "" { path = os.Getenv("CONFIG_FILE") } if path == "" { path = defaultConfigPath } contents, err := os.ReadFile(path) if err != nil { return Config{}, fmt.Errorf("read config %q: %w", path, err) } var config Config if err := yaml.Unmarshal(contents, &config); err != nil { return Config{}, fmt.Errorf("parse config %q: %w", path, err) } applyEnvironment(&config) if err := config.Validate(); err != nil { return Config{}, fmt.Errorf("validate config: %w", err) } return config, nil } func (config Config) Validate() error { var missing []string if strings.TrimSpace(config.Environment) == "" { missing = append(missing, "environment") } if strings.TrimSpace(config.HTTP.Address) == "" { missing = append(missing, "http.address") } if strings.TrimSpace(config.Database.URL) == "" { missing = append(missing, "database.url") } if len(missing) > 0 { return errors.New("missing required values: " + strings.Join(missing, ", ")) } return nil } func applyEnvironment(config *Config) { if value := os.Getenv("APP_ENVIRONMENT"); value != "" { config.Environment = value } if value := os.Getenv("APP_HTTP_ADDRESS"); value != "" { config.HTTP.Address = value } if value := os.Getenv("APP_DATABASE_URL"); value != "" { config.Database.URL = value } }