Files
Alvand-P/lib/opendax/config_loader.rb
2026-08-13 19:42:08 +03:30

72 lines
2.1 KiB
Ruby

# 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