@@ -24,23 +24,39 @@ class ConfigLoader
2424 normalize_headers : true
2525 } . freeze
2626
27+ SILENCE_CONFIG_LOADER_MESSAGES = ENV . fetch (
28+ "HOOKS_SILENCE_CONFIG_LOADER_MESSAGES" , "false"
29+ ) . downcase == "true" . freeze
30+
2731 # Load and merge configuration from various sources
2832 #
2933 # @param config_path [String, Hash] Path to config file or config hash
3034 # @return [Hash] Merged configuration
3135 def self . load ( config_path : nil )
3236 config = DEFAULT_CONFIG . dup
37+ overrides = [ ]
3338
3439 # Load from file if path provided
3540 if config_path . is_a? ( String ) && File . exist? ( config_path )
3641 file_config = load_config_file ( config_path )
37- config . merge! ( file_config ) if file_config
38- elsif config_path . is_a? ( Hash )
39- config . merge! ( config_path )
42+ if file_config
43+ overrides << "file config"
44+ config . merge! ( file_config )
45+ end
4046 end
4147
42- # Override with environment variables
43- config . merge! ( load_env_config )
48+ # Override with environment variables (before programmatic config)
49+ env_config = load_env_config
50+ if env_config . any?
51+ overrides << "environment variables"
52+ config . merge! ( env_config )
53+ end
54+
55+ # Programmatic config has highest priority
56+ if config_path . is_a? ( Hash )
57+ overrides << "programmatic config"
58+ config . merge! ( config_path )
59+ end
4460
4561 # Convert string keys to symbols for consistency
4662 config = symbolize_keys ( config )
@@ -51,6 +67,11 @@ def self.load(config_path: nil)
5167 config [ :production ] = false
5268 end
5369
70+ # Log overrides if any were made
71+ if overrides . any?
72+ puts "INFO: Configuration overrides applied from: #{ overrides . join ( ', ' ) } " unless SILENCE_CONFIG_LOADER_MESSAGES
73+ end
74+
5475 return config
5576 end
5677
@@ -93,8 +114,9 @@ def self.load_config_file(file_path)
93114 end
94115
95116 result
96- rescue => _e
97- # In production, we'd log this error
117+ rescue => e
118+ # Log this error with meaningful information
119+ puts "ERROR: Failed to load config file '#{ file_path } ': #{ e . message } " unless SILENCE_CONFIG_LOADER_MESSAGES
98120 nil
99121 end
100122
@@ -105,26 +127,35 @@ def self.load_env_config
105127 env_config = { }
106128
107129 env_mappings = {
130+ "HOOKS_HANDLER_DIR" => :handler_dir ,
108131 "HOOKS_HANDLER_PLUGIN_DIR" => :handler_plugin_dir ,
109132 "HOOKS_AUTH_PLUGIN_DIR" => :auth_plugin_dir ,
133+ "HOOKS_LIFECYCLE_PLUGIN_DIR" => :lifecycle_plugin_dir ,
134+ "HOOKS_INSTRUMENTS_PLUGIN_DIR" => :instruments_plugin_dir ,
110135 "HOOKS_LOG_LEVEL" => :log_level ,
111136 "HOOKS_REQUEST_LIMIT" => :request_limit ,
112137 "HOOKS_REQUEST_TIMEOUT" => :request_timeout ,
113138 "HOOKS_ROOT_PATH" => :root_path ,
114139 "HOOKS_HEALTH_PATH" => :health_path ,
115140 "HOOKS_VERSION_PATH" => :version_path ,
116141 "HOOKS_ENVIRONMENT" => :environment ,
117- "HOOKS_ENDPOINTS_DIR" => :endpoints_dir
142+ "HOOKS_ENDPOINTS_DIR" => :endpoints_dir ,
143+ "HOOKS_USE_CATCHALL_ROUTE" => :use_catchall_route ,
144+ "HOOKS_SYMBOLIZE_PAYLOAD" => :symbolize_payload ,
145+ "HOOKS_NORMALIZE_HEADERS" => :normalize_headers
118146 }
119147
120148 env_mappings . each do |env_key , config_key |
121149 value = ENV [ env_key ]
122150 next unless value
123151
124- # Convert numeric values
152+ # Convert values to appropriate types
125153 case config_key
126154 when :request_limit , :request_timeout
127155 env_config [ config_key ] = value . to_i
156+ when :use_catchall_route , :symbolize_payload , :normalize_headers
157+ # Convert string to boolean
158+ env_config [ config_key ] = %w[ true 1 yes on ] . include? ( value . downcase )
128159 else
129160 env_config [ config_key ] = value
130161 end
0 commit comments