fix: prevent Config DEFAULTS mutation through deep_merge
Freeze inner hashes in DEFAULTS and rewrite deep_merge to build fresh
hashes with {}.merge for uncopied inner maps. Previously, inner hashes
shared objects with the frozen-looking DEFAULTS, so mutating a loaded
config silently poisoned all later loads.
This commit is contained in:
+24
-9
@@ -10,7 +10,7 @@ module Volumen
|
||||
DEFAULT_PATH = "/etc/volumen/config.toml"
|
||||
|
||||
DEFAULTS = {
|
||||
"server" => { "host" => "0.0.0.0", "port" => 9090 },
|
||||
"server" => { "host" => "0.0.0.0", "port" => 9090 }.freeze,
|
||||
"content_dir" => "/var/lib/volumen/posts",
|
||||
"users_file" => "/var/lib/volumen/users.toml",
|
||||
"site" => {
|
||||
@@ -19,12 +19,12 @@ module Volumen
|
||||
"base_url" => "https://example.com",
|
||||
"language" => "en",
|
||||
"author" => "Anonymous"
|
||||
},
|
||||
}.freeze,
|
||||
"admin" => {
|
||||
"password_hash" => "",
|
||||
"session_key" => "",
|
||||
"session_ttl" => 86_400
|
||||
}
|
||||
}.freeze
|
||||
}.freeze
|
||||
|
||||
TEMPLATE = <<~TOML
|
||||
@@ -77,12 +77,27 @@ module Volumen
|
||||
end
|
||||
|
||||
def self.deep_merge(base, override)
|
||||
base.merge(override) do |_key, base_val, override_val|
|
||||
if base_val.is_a?(Hash) && override_val.is_a?(Hash)
|
||||
deep_merge(base_val, override_val)
|
||||
else
|
||||
override_val
|
||||
end
|
||||
result = {}
|
||||
base.each do |key, base_val|
|
||||
result[key] = if override.key?(key)
|
||||
merge_leaf(base_val, override[key])
|
||||
elsif base_val.is_a?(Hash)
|
||||
{}.merge(base_val)
|
||||
else
|
||||
base_val
|
||||
end
|
||||
end
|
||||
override.each do |key, val|
|
||||
result[key] = val unless result.key?(key)
|
||||
end
|
||||
result
|
||||
end
|
||||
|
||||
def self.merge_leaf(base_val, override_val)
|
||||
if base_val.is_a?(Hash) && override_val.is_a?(Hash)
|
||||
deep_merge(base_val, override_val)
|
||||
else
|
||||
override_val
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -27,6 +27,14 @@ class ConfigTest < Minitest::Test
|
||||
assert_equal "0.0.0.0", Volumen::Config.load(path: MISSING).host
|
||||
end
|
||||
|
||||
def test_does_not_mutate_default_inner_hashes
|
||||
config = Volumen::Config.load(path: MISSING)
|
||||
config.site["title"] = "MUTATED"
|
||||
fresh = Volumen::Config.load(path: MISSING)
|
||||
assert_equal "My Blog", fresh.site["title"],
|
||||
"mutating a loaded config must not poison DEFAULTS for later loads"
|
||||
end
|
||||
|
||||
def test_loads_and_merges_file
|
||||
Dir.mktmpdir do |dir|
|
||||
path = File.join(dir, "config.toml")
|
||||
|
||||
Reference in New Issue
Block a user