aa7d78d9b1
Followup: https://github.com/rails/rails/pull/47023 ``` Shape Edges Report ----------------------------------- snip... 238 @errors snip... 219 @options snip... 129 @_request 128 @type 125 @virtual_path 124 @_assigns 123 @_config 123 @_controller 123 @output_buffer 123 @view_flow 122 @_default_form_builder snip... 89 @_already_called 75 @validation_context snip... 65 @_new_record_before_last_commit snip... 58 @_url_options snip... ```
103 lines
2.9 KiB
Ruby
103 lines
2.9 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
require "yaml"
|
|
require "active_support/encrypted_file"
|
|
require "active_support/ordered_options"
|
|
require "active_support/core_ext/object/inclusion"
|
|
require "active_support/core_ext/module/delegation"
|
|
|
|
module ActiveSupport
|
|
# Provides convenience methods on top of EncryptedFile to access values stored
|
|
# as encrypted YAML.
|
|
#
|
|
# Values can be accessed via +Hash+ methods, such as +fetch+ and +dig+, or via
|
|
# dynamic accessor methods, similar to OrderedOptions.
|
|
#
|
|
# my_config = ActiveSupport::EncryptedConfiguration.new(...)
|
|
# my_config.read # => "some_secret: 123\nsome_namespace:\n another_secret: 456"
|
|
#
|
|
# my_config[:some_secret]
|
|
# # => 123
|
|
# my_config.some_secret
|
|
# # => 123
|
|
# my_config.dig(:some_namespace, :another_secret)
|
|
# # => 456
|
|
# my_config.some_namespace.another_secret
|
|
# # => 456
|
|
# my_config.fetch(:foo)
|
|
# # => KeyError
|
|
# my_config.foo!
|
|
# # => KeyError
|
|
#
|
|
class EncryptedConfiguration < EncryptedFile
|
|
class InvalidContentError < RuntimeError
|
|
def initialize(content_path)
|
|
super "Invalid YAML in '#{content_path}'."
|
|
end
|
|
|
|
def message
|
|
cause.is_a?(Psych::SyntaxError) ? "#{super}\n\n #{cause.message}" : super
|
|
end
|
|
end
|
|
|
|
delegate :[], :fetch, to: :config
|
|
delegate_missing_to :options
|
|
|
|
def initialize(config_path:, key_path:, env_key:, raise_if_missing_key:)
|
|
super content_path: config_path, key_path: key_path,
|
|
env_key: env_key, raise_if_missing_key: raise_if_missing_key
|
|
@config = nil
|
|
@options = nil
|
|
end
|
|
|
|
# Reads the file and returns the decrypted content. See EncryptedFile#read.
|
|
def read
|
|
super
|
|
rescue ActiveSupport::EncryptedFile::MissingContentError
|
|
# Allow a config to be started without a file present
|
|
""
|
|
end
|
|
|
|
def validate! # :nodoc:
|
|
deserialize(read)
|
|
end
|
|
|
|
# Returns the decrypted content as a Hash with symbolized keys.
|
|
#
|
|
# my_config = ActiveSupport::EncryptedConfiguration.new(...)
|
|
# my_config.read # => "some_secret: 123\nsome_namespace:\n another_secret: 456"
|
|
#
|
|
# my_config.config
|
|
# # => { some_secret: 123, some_namespace: { another_secret: 789 } }
|
|
#
|
|
def config
|
|
@config ||= deserialize(read).deep_symbolize_keys
|
|
end
|
|
|
|
private
|
|
def deep_transform(hash)
|
|
return hash unless hash.is_a?(Hash)
|
|
|
|
h = ActiveSupport::InheritableOptions.new
|
|
hash.each do |k, v|
|
|
h[k] = deep_transform(v)
|
|
end
|
|
h
|
|
end
|
|
|
|
def options
|
|
@options ||= ActiveSupport::InheritableOptions.new(deep_transform(config))
|
|
end
|
|
|
|
def deserialize(content)
|
|
config = YAML.respond_to?(:unsafe_load) ?
|
|
YAML.unsafe_load(content, filename: content_path) :
|
|
YAML.load(content, filename: content_path)
|
|
|
|
config.presence || {}
|
|
rescue Psych::SyntaxError
|
|
raise InvalidContentError.new(content_path)
|
|
end
|
|
end
|
|
end
|