13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
|
# File 'lib/itsi/server/config/typed_struct.rb', line 13
def self.new(defaults = nil, &defaults_blk)
defaults = TypedStruct.module_eval(&defaults_blk) if defaults_blk
return defaults unless defaults.is_a?(Hash)
defaults.transform_values! { _1.is_a?(Validation) ? _1.default(nil) : _1 }
Struct.new(*defaults.keys, keyword_init: true) do
define_method(:initialize) do |*input, validate: true, **raw_input|
raise "─ Invalid input to #{self}: #{input.last}" if input.last && !input.last.is_a?(Hash)
raw_input.transform_keys! { |k| k.to_s.downcase.to_sym }
raw_input.merge!(input.pop.transform_keys! { |k| k.to_s.downcase.to_sym }) if input.last.is_a?(Hash)
excess_keys = raw_input.keys - defaults.keys
raise "─ Unsupported keys #{excess_keys}" if excess_keys.any?
initial_values = defaults.each_with_object({}) do |(k, default_config), inputs|
value = raw_input.key?(k) ? raw_input[k] : default_config[VALUE].dup
next inputs[k] = value unless validate
begin
inputs[k] = default_config[VALIDATE].validate!(value)
rescue StandardError => e
raise ArgumentError, "─ #{k}: #{e.message}"
end
end
super(initial_values)
validator = self.class.instance_eval { @validators }
instance_eval(&validator) if validator
end
define_singleton_method(:validate) do |&blk|
@validators = blk
end
defaults.each do |key, (_, validation)|
define_method(:"#{key}?") do
!!self[key]
end
define_method(:"#{key}=") do |value|
self[key] = validation.validate!(value)
end
end
define_method(:[]=) do |key, value|
super(key, defaults[key][VALIDATE].validate!(value))
end
def is_typed_struct?
true
end
def merge_config(other)
self.class.new(members.map do |key|
value = self[key]
has_merged_val = (other.is_a?(Hash) ? other.key?(key) : other.member?(key))
next [key, value] unless has_merged_val
[
key,
TypedStruct.typed_struct?(value) ? value.merge_config(other[key]) : other[key]
]
end.to_h)
end
def to_h
super.transform_values { |v| v.respond_to?(:merge_config) ? v.to_h : v }
end
end
end
|