@@ -53,6 +53,24 @@ type AutoDiscoveryConfig struct {
5353 // Defaults to true
5454 EnableAppScoping bool
5555
56+ // Environment Variable Source Configuration
57+ // EnableEnvSource enables loading config from environment variables
58+ // Defaults to true
59+ EnableEnvSource bool
60+
61+ // EnvPrefix is the prefix for environment variables
62+ // If empty, defaults to AppName uppercase with trailing underscore
63+ EnvPrefix string
64+
65+ // EnvSeparator is the separator for nested keys in env vars
66+ // Defaults to "_"
67+ EnvSeparator string
68+
69+ // EnvOverridesFile controls whether env vars override file config values
70+ // When true, env source gets higher priority than file sources
71+ // Defaults to true
72+ EnvOverridesFile bool
73+
5674 // Logger for discovery operations
5775 Logger logger.Logger
5876
@@ -87,6 +105,10 @@ func DefaultAutoDiscoveryConfig() AutoDiscoveryConfig {
87105 RequireBase : false ,
88106 RequireLocal : false ,
89107 EnableAppScoping : true ,
108+ // Environment variable source defaults
109+ EnableEnvSource : true ,
110+ EnvSeparator : "_" ,
111+ EnvOverridesFile : true ,
90112 }
91113}
92114
@@ -115,6 +137,11 @@ func DiscoverAndLoadConfigs(cfg AutoDiscoveryConfig) (ConfigManager, *AutoDiscov
115137 cfg .SearchPaths = []string {cwd }
116138 }
117139
140+ // Default env separator
141+ if cfg .EnvSeparator == "" {
142+ cfg .EnvSeparator = "_"
143+ }
144+
118145 // Discover config files
119146 result , err := discoverConfigFiles (cfg )
120147 if err != nil {
@@ -127,6 +154,12 @@ func DiscoverAndLoadConfigs(cfg AutoDiscoveryConfig) (ConfigManager, *AutoDiscov
127154 ErrorHandler : cfg .ErrorHandler ,
128155 })
129156
157+ // Priority scheme:
158+ // - Base config: 100
159+ // - Local config: 200
160+ // - Environment (if EnvOverridesFile=true): 300
161+ // - Environment (if EnvOverridesFile=false): 50
162+
130163 // Load base config if found
131164 if result .BaseConfigPath != "" {
132165 source , err := sources .NewFileSource (result .BaseConfigPath , sources.FileSourceOptions {
@@ -179,8 +212,59 @@ func DiscoverAndLoadConfigs(cfg AutoDiscoveryConfig) (ConfigManager, *AutoDiscov
179212 return nil , nil , errors .New ("local config file required but not found" )
180213 }
181214
215+ // Load environment variable source if enabled
216+ if cfg .EnableEnvSource {
217+ // Determine env prefix - default to AppName uppercase with trailing underscore
218+ envPrefix := cfg .EnvPrefix
219+ if envPrefix == "" && cfg .AppName != "" {
220+ envPrefix = strings .ToUpper (cfg .AppName ) + cfg .EnvSeparator
221+ }
222+
223+ // Determine priority based on EnvOverridesFile setting
224+ envPriority := 300 // Higher than file sources (default: env overrides files)
225+ if ! cfg .EnvOverridesFile {
226+ envPriority = 50 // Lower than file sources (files override env)
227+ }
228+
229+ envSource , err := sources .NewEnvSource (envPrefix , sources.EnvSourceOptions {
230+ Name : "config.env" ,
231+ Prefix : envPrefix ,
232+ Priority : envPriority ,
233+ Separator : cfg .EnvSeparator ,
234+ WatchEnabled : false , // Env watching is expensive, disabled by default
235+ CaseSensitive : false ,
236+ IgnoreEmpty : true ,
237+ TypeConversion : true ,
238+ Logger : cfg .Logger ,
239+ ErrorHandler : cfg .ErrorHandler ,
240+ })
241+ if err != nil {
242+ if cfg .Logger != nil {
243+ cfg .Logger .Warn ("failed to create env config source" ,
244+ F ("error" , err .Error ()),
245+ )
246+ }
247+ } else {
248+ if err := manager .LoadFrom (envSource ); err != nil {
249+ if cfg .Logger != nil {
250+ cfg .Logger .Warn ("failed to load env config source" ,
251+ F ("error" , err .Error ()),
252+ )
253+ }
254+ } else {
255+ if cfg .Logger != nil {
256+ cfg .Logger .Debug ("loaded environment variable config source" ,
257+ F ("prefix" , envPrefix ),
258+ F ("priority" , envPriority ),
259+ F ("overrides_files" , cfg .EnvOverridesFile ),
260+ )
261+ }
262+ }
263+ }
264+ }
265+
182266 // Extract app-scoped config if enabled and AppName is provided
183- // We need to do this BEFORE merging sources to maintain proper priority
267+ // We need to do this AFTER loading all sources to maintain proper priority
184268 if cfg .EnableAppScoping && cfg .AppName != "" {
185269 // Get the source data before merging
186270 if mgr , ok := manager .(* Manager ); ok {
0 commit comments