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 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187
|
# 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 reverse_merge!(other)
@options = to_hash.reverse_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__}`"
log_and_raise_dev_exception(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__}`"
log_and_raise_dev_exception(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}`"
log_and_raise_dev_exception(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
private
# We can't call Gitlab::ErrorTracking.track_and_raise_for_dev_exception
# because that method will attempt to load ApplicationContext and
# fail to load User since the Devise is not yet set up in
# `config/initialiers/8_devise.rb`.
def log_and_raise_dev_exception(message, extra = {})
raise message unless Rails.env.production?
# Gitlab::BacktraceCleaner drops config/initializers, so we just limit the
# backtrace to the first 10 lines.
payload = extra.merge(message: message, caller: caller[0..10])
Gitlab::AppJsonLogger.warn(payload)
end
end
end
|