mirror of
https://gitlab.com/gitlab-org/gitlab-foss.git
synced 2025-07-25 16:03:48 +00:00
188 lines
4.7 KiB
Ruby
188 lines
4.7 KiB
Ruby
# 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
|