1
2
3
4
5
6
7
8
9
10
11
12
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
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
|
# frozen_string_literal: true
require 'forwardable'
module GitlabSettings
class Options
extend Forwardable
def_delegators :@options,
:count,
:deep_stringify_keys,
:deep_symbolize_keys,
:default_proc,
:dig,
:each_key,
:each_pair,
:each_value,
:each,
:empty?,
:fetch_values,
:fetch,
:filter,
:keys,
:length,
:map,
:member?,
:merge,
:reject,
:select,
:size,
:slice,
:stringify_keys,
:symbolize_keys,
:transform_keys,
:transform_values,
:value?,
:values_at,
:values
# Recursively build GitlabSettings::Options
def self.build(obj)
case obj
when Hash
new(obj.transform_values { |value| build(value) })
when Array
obj.map { |value| build(value) }
else
obj
end
end
def initialize(value)
@options = value.deep_stringify_keys
end
def [](key)
@options[key.to_s]
end
def []=(key, value)
@options[key.to_s] = self.class.build(value)
end
def key?(key)
@options.key?(key.to_s)
end
alias_method :has_key?, :key?
# Some configurations use the 'default' key, like:
# https://gitlab.com/gitlab-org/gitlab/-/blob/c4d5c77c87494bb320fa7fdf19b0e4d7d52af1d1/spec/support/helpers/stub_configuration.rb#L96
# But since `default` is also a method in Hash, this can be confusing and
# raise an exception instead of returning nil, as expected in some places.
# To avoid that, we use #default always as a possible internal key
def default
@options['default']
end
# For backward compatibility, like:
# https://gitlab.com/gitlab-org/gitlab/-/blob/adf67e90428670aaa955731f3bdeafb8b3a874cd/lib/gitlab/database/health_status/indicators/patroni_apdex.rb#L58
def with_indifferent_access
to_hash.with_indifferent_access
end
def dup
self.class.build(to_hash)
end
def merge(other)
self.class.build(to_hash.merge(other.deep_stringify_keys))
end
def merge!(other)
@options = to_hash.merge(other.deep_stringify_keys)
end
def deep_merge(other)
self.class.build(to_hash.deep_merge(other.deep_stringify_keys))
end
def deep_merge!(other)
@options = to_hash.deep_merge(other.deep_stringify_keys)
end
def is_a?(klass)
return true if klass == Hash
super(klass)
end
def to_hash
@options.deep_transform_values do |option|
case option
when self.class
option.to_hash
else
option
end
end
end
alias_method :to_h, :to_hash
# Don't alter the internal keys
def stringify_keys!
error_msg = "Warning: Do not mutate #{self.class} objects: `#{__method__}`"
Gitlab::ErrorTracking.track_and_raise_for_dev_exception(RuntimeError.new(error_msg), method: __method__)
to_hash.deep_stringify_keys
end
alias_method :deep_stringify_keys!, :stringify_keys!
# Don't alter the internal keys
def symbolize_keys!
error_msg = "Warning: Do not mutate #{self.class} objects: `#{__method__}`"
Gitlab::ErrorTracking.track_and_raise_for_dev_exception(RuntimeError.new(error_msg), method: __method__)
to_hash.deep_symbolize_keys
end
alias_method :deep_symbolize_keys!, :symbolize_keys!
def method_missing(name, *args, &block)
name_string = +name.to_s
if name_string.chomp!("=")
return self[name_string] = args.first if key?(name_string)
elsif key?(name_string)
return self[name_string]
end
if @options.respond_to?(name)
error_msg = "Calling a hash method on #{self.class}: `#{name}`"
Gitlab::ErrorTracking.track_and_raise_for_dev_exception(RuntimeError.new(error_msg), method: name)
return @options.public_send(name, *args, &block) # rubocop: disable GitlabSecurity/PublicSend
end
raise ::GitlabSettings::MissingSetting, "option '#{name}' not defined"
end
def respond_to_missing?(name, include_all = false)
return true if key?(name)
@options.respond_to?(name, include_all)
end
end
end
|