Initial commit
This commit is contained in:
71
lib/opendax/config_loader.rb
Normal file
71
lib/opendax/config_loader.rb
Normal file
@@ -0,0 +1,71 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'yaml'
|
||||
|
||||
module Opendax
|
||||
# Loads config/app.yml and deep-merges an environment overlay when present.
|
||||
#
|
||||
# Overlay resolution (first match wins):
|
||||
# 1. OPENDAX_ENV=local|staging|production → config/app.{env}.yml
|
||||
# 2. If OPENDAX_ENV unset and config/app.local.yml exists → use local overlay
|
||||
#
|
||||
# Never commit real secrets to config/app.yml — use app.local.yml or app.production.yml.
|
||||
module ConfigLoader
|
||||
BASE_PATH = 'config/app.yml'
|
||||
|
||||
module_function
|
||||
|
||||
def load
|
||||
base = load_yaml_hash!(BASE_PATH, 'base config')
|
||||
overlay_path = resolve_overlay_path
|
||||
return base unless overlay_path && File.exist?(overlay_path)
|
||||
|
||||
overlay = load_yaml_hash!(overlay_path, 'overlay config')
|
||||
deep_merge(base, overlay)
|
||||
end
|
||||
|
||||
def load_yaml_hash!(path, label)
|
||||
data = YAML.load_file(path)
|
||||
return data if data.is_a?(Hash)
|
||||
|
||||
raise TypeError,
|
||||
"#{label} #{path} must be a YAML mapping (Hash), got #{data.class}. " \
|
||||
'Check syntax — quote values that contain colons (e.g. database passwords).'
|
||||
end
|
||||
|
||||
def resolve_overlay_path
|
||||
env = ENV['OPENDAX_ENV'].to_s.strip
|
||||
if env.empty?
|
||||
return 'config/app.local.yml' if File.exist?('config/app.local.yml')
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
"config/app.#{env}.yml"
|
||||
end
|
||||
|
||||
def active_overlay
|
||||
path = resolve_overlay_path
|
||||
path if path && File.exist?(path)
|
||||
end
|
||||
|
||||
def deep_merge(base, overlay)
|
||||
unless base.is_a?(Hash) && overlay.is_a?(Hash)
|
||||
raise TypeError,
|
||||
"deep_merge expects Hash + Hash, got #{base.class} + #{overlay.class}"
|
||||
end
|
||||
|
||||
base.merge(overlay) do |key, old_val, new_val|
|
||||
if old_val.is_a?(Hash) && new_val.is_a?(Hash)
|
||||
deep_merge(old_val, new_val)
|
||||
elsif old_val.is_a?(Hash) && !new_val.is_a?(Hash)
|
||||
raise TypeError,
|
||||
"Config type mismatch at #{key}: base has Hash but overlay has " \
|
||||
"#{new_val.class}. Quote scalar values in app.staging.yml (passwords with ':' need quotes)."
|
||||
else
|
||||
new_val
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user