diff --git a/system/container.rb b/system/container.rb index d44ba87..50c6478 100644 --- a/system/container.rb +++ b/system/container.rb @@ -3,12 +3,14 @@ require 'dry/system/container' require 'dry/system/hanami' require_relative '../lib/types' +require_relative './settings' # General container class for project dependencies # # {http://dry-rb.org/gems/dry-system/ Dry-system documentation} class Container < Dry::System::Container extend Dry::System::Hanami::Resolver + setting :settings # use :bootsnap use :env @@ -32,7 +34,24 @@ class Container < Dry::System::Container # polls register_folder! 'polls/operations' + configure do |config| config.env = Hanami.env + config.settings = Settings.load(root, config.env) + end + + def self.settings + config.settings + end + + # Combines a settings hash from keys prefixed with `scope` value + # @param scope [#to_s] + # @return [Hash{Symbol=>Object}] + def self.settings_for(scope) + hash = settings.to_hash + prefix = /^#{Regexp.escape(scope.to_s)}_/ + hash.keys.grep(prefix).each_with_object({}) do |key, result| + result[key.to_s.gsub(prefix, '').to_sym] = hash[key] + end end end diff --git a/system/hanami_settings.rb b/system/hanami_settings.rb new file mode 100644 index 0000000..025c910 --- /dev/null +++ b/system/hanami_settings.rb @@ -0,0 +1,79 @@ +# frozen_string_literal: true + +require 'dotenv' + +class Dry::System::Hanami::Settings + SettingValueError = Class.new(StandardError) + + module Types + include Dry::Types.module + + module Required + String = Types::Strict::String.constrained(min_size: 1) + end + + module Optional + String = Required::String.optional + end + end + + def self.warn_generated(setting, value) + env = setting.to_s.upcase + ENV[env] = value + warn <<~ERROR + Please, set #{env} in `.env`: + + #{env}=#{value.inspect} + + ERROR + value + end + + def self.schema + @schema ||= {} + end + + def self.setting(name, type = nil) + settings(name => type) + end + + def self.settings(new_schema) + check_schema_duplication(new_schema) + @schema = schema.merge(new_schema) + + self + end + + def self.check_schema_duplication(new_schema) + shared_keys = new_schema.keys & schema.keys + + raise ArgumentError, "Setting :#{shared_keys.first} has already been defined" if shared_keys.any? + end + private_class_method :check_schema_duplication + + def self.load(root, env) + build_config(schema, dotenv_data(root, env)) + end + + # @param [Settings] schema + # @param [Hash] defaults + # @return [Dry::Configurable::Config] + def self.build_config(schema, defaults = {}) + Class.new do + extend Dry::Configurable + + schema.each do |key, type| + env_var = key.to_s.upcase + value = ENV.fetch(env_var) { defaults[env_var] } + + begin + value = type[value] if type + rescue StandardError => e + raise SettingValueError, "error typecasting +#{key}+: #{e}" + end + + setting key, value + end + end.config + end +end diff --git a/system/settings.rb b/system/settings.rb new file mode 100644 index 0000000..829f758 --- /dev/null +++ b/system/settings.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +require_relative 'hanami_settings' + +class Settings < Dry::System::Hanami::Settings + setting :database_url, Types::Required::String.default('postgres://test@db/test') + + setting :dependency_base_url, Types::Required::String.default('url') + setting :dependency_something_else, Types::Required::String.default('something else') +end