Files
gitlabhq/lib/gitlab_settings/options.rb
2024-08-23 09:09:28 +00:00

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