lib/ultra_settings/configuration.rb in ultra_settings-1.0.1 vs lib/ultra_settings/configuration.rb in ultra_settings-1.1.0
- old
+ new
@@ -31,14 +31,15 @@
# By default this will be the underscored name of the class plus an underscore plus the field name
# all in uppercase (i.e. MyServiceConfiguration#foo becomes "MY_SERVICE_FOO").
# @param yaml_key [String, Symbol] The name of the YAML key to use for the field. By default
# this is the name of the field.
# @return [void]
- def field(name, type: :string, description: nil, default: nil, default_if: nil, static: nil, runtime_setting: nil, env_var: nil, yaml_key: nil)
+ def field(name, type: :string, description: nil, default: nil, default_if: nil, static: nil, secret: nil, runtime_setting: nil, env_var: nil, yaml_key: nil)
name = name.to_s
type = type.to_sym
static = !!static
+ secret = lambda { fields_secret_by_default? } if secret.nil?
unless name.match?(ALLOWED_NAME_PATTERN)
raise ArgumentError.new("Invalid name: #{name.inspect}")
end
@@ -57,21 +58,22 @@
default: default,
default_if: default_if,
env_var: construct_env_var(name, env_var),
runtime_setting: (static ? nil : construct_runtime_setting(name, runtime_setting)),
yaml_key: construct_yaml_key(name, yaml_key),
- static: static
+ static: static,
+ secret: secret
)
class_eval <<-RUBY, __FILE__, __LINE__ + 1 # rubocop:disable Security/Eval
def #{name}
__get_value__(#{name.inspect})
end
RUBY
if type == :boolean
- alias_method "#{name}?", name
+ alias_method :"#{name}?", name
end
end
# List of the defined fields for the configuration.
#
@@ -304,10 +306,27 @@
# @return [String]
def yaml_config_env
get_inheritable_class_attribute(:@yaml_config_env, "development")
end
+ # Sets the default value for the secret property of fields. Individual fields can still
+ # override this value by explicitly setting the secret property. By default, fields are
+ # considered secret.
+ #
+ # @param value [Boolean]
+ # @return [void]
+ def fields_secret_by_default=(value)
+ set_inheritable_class_attribute(:@fields_secret_by_default, !!value)
+ end
+
+ # Check if fields are considered secret by default.
+ #
+ # @return [Boolean]
+ def fields_secret_by_default?
+ get_inheritable_class_attribute(:@fields_secret_by_default, true)
+ end
+
# Override field values within a block.
#
# @param values [Hash<Symbol, Object>]] List of fields with the values they
# should return within the block.
# @return [Object] The value returned by the block.
@@ -448,16 +467,65 @@
@override_values[Thread.current.object_id] = save_val
end
end
end
+ # Get the current source for the field.
+ #
+ # @param name [String, Symbol] the name of the field.
+ # @return [Symbol, nil] The source of the value (:env, :settings, :yaml, or :default).
def __source__(name)
- field = self.class.send(:defined_fields)[name]
+ field = self.class.send(:defined_fields)[name.to_s]
+ raise ArgumentError.new("Unknown field: #{name.inspect}") unless field
+
source = field.source(env: ENV, settings: UltraSettings.__runtime_settings__, yaml_config: __yaml_config__)
source || :default
end
+ # Get the value of the field from the specified source.
+ #
+ # @param name [String, Symbol] the name of the field.
+ # @param source [Symbol] the source of the value (:env, :settings, :yaml, or :default).
+ # @return [Object] The value of the field.
+ def __value_from_source__(name, source)
+ field = self.class.send(:defined_fields)[name.to_s]
+ raise ArgumentError.new("Unknown field: #{name.inspect}") unless field
+
+ case source
+ when :env
+ field.value(env: ENV)
+ when :settings
+ field.value(settings: UltraSettings.__runtime_settings__)
+ when :yaml
+ field.value(yaml_config: __yaml_config__)
+ when :default
+ field.default
+ else
+ raise ArgumentError.new("Unknown source: #{source.inspect}")
+ end
+ end
+
+ # Output the current state of the configuration as a hash. If the field is marked as a secret,
+ # then the value will be a secure hash of the value instead of the value itself.
+ #
+ # The intent of this method is to provide a serializable value that captures the current state
+ # of the configuration without exposing any secrets. You could, for instance, use the output
+ # to compare the configuration of you application between two different environments.
+ #
+ # @return [Hash]
+ def __to_hash__
+ payload = {}
+ self.class.fields.each do |field|
+ value = self[field.name]
+ if field.secret? && !value.nil?
+ value = "securehash:#{Digest::MD5.hexdigest(Digest::SHA256.hexdigest(value.to_s))}"
+ end
+ payload[field.name] = value
+ end
+ payload
+ end
+
private
def __get_value__(name)
field = self.class.send(:defined_fields)[name]
return nil unless field
@@ -508,9 +576,9 @@
false
end
end
def __yaml_config__
- @yaml_config ||= (self.class.load_yaml_config || {})
+ @yaml_config ||= self.class.load_yaml_config || {}
end
end
end