diff --git a/lib/resource_registry.rb b/lib/resource_registry.rb index 6f0a5286..10e0ed75 100644 --- a/lib/resource_registry.rb +++ b/lib/resource_registry.rb @@ -1,12 +1,13 @@ require 'dry/transaction' require 'dry/transaction/operation' +require 'dry/monads/result' require 'system/boot' require 'resource_registry/error' require 'resource_registry/types' require 'resource_registry/entities' require 'resource_registry/services' require 'resource_registry/stores' -require 'resource_registry/validations' +require File.expand_path(File.join(File.dirname(__FILE__), 'resource_registry/validations')) require 'resource_registry/registries' require 'resource_registry/serializers' require 'resource_registry/version' @@ -26,8 +27,11 @@ def configure end def load_options(dir) - Dir.glob(File.join(dir, "*")).each do |file_path| - ResourceRegistry::Services::LoadRegistryOptions.new.call(file_path) + files_to_load = Dir.glob(File.join(dir, "*")).to_a + files_to_load.inject(Dry::Monads::Success(:ok)) do |result, file_path| + result.bind do |_ignore| + ResourceRegistry::Services::LoadRegistryOptions.new.call(file_path) + end end end diff --git a/lib/resource_registry/entities.rb b/lib/resource_registry/entities.rb index ef191bb4..f834503e 100644 --- a/lib/resource_registry/entities.rb +++ b/lib/resource_registry/entities.rb @@ -1,8 +1,4 @@ require 'dry-struct' unless defined?(Dry::Struct) -require 'resource_registry/types' -require 'resource_registry/entities/dry_struct_setters' -require 'resource_registry/entities/setting' -require 'resource_registry/entities/option' require 'resource_registry/entities/registry' require 'resource_registry/entities/core_options' require 'resource_registry/entities/qualifying_life_event' diff --git a/lib/resource_registry/entities/option.rb b/lib/resource_registry/entities/option.rb index c398021d..249d9a87 100644 --- a/lib/resource_registry/entities/option.rb +++ b/lib/resource_registry/entities/option.rb @@ -1,19 +1,28 @@ +require 'resource_registry/entities/setting' + module ResourceRegistry module Entities - class Option < Dry::Struct - include Enumerable - include DryStructSetters - - transform_keys(&:to_sym) + OptionConstructor = Types.Constructor("Option") { |val| Option.new(val) rescue nil } - # attribute :parent_namespace?, Types::Symbol - attribute :namespace?, Types::Symbol - attribute :key, Types::Symbol + class Option + extend Dry::Initializer + include Enumerable + option :namespace, type: Dry::Types["coercible.symbol"], optional: true + option :key, type: Dry::Types["coercible.symbol"], optional: true # TODO: Make settings attribute dynamically typed - attribute :settings?, Types::Array.of(ResourceRegistry::Entities::Setting) - attribute :namespaces?, Types::Array.of(ResourceRegistry::Entities::Option) + option :settings, type: Dry::Types['coercible.array'].of(SettingConstructor), optional: true, default: -> { [] } + option :namespaces, Dry::Types['coercible.array'].of(OptionConstructor), optional: true, default: -> { [] } + + def each + settings.each do |s| + yield s + end + namespaces.each do |ns| + yield ns + end + end end end end \ No newline at end of file diff --git a/lib/resource_registry/entities/registry.rb b/lib/resource_registry/entities/registry.rb index b91873c6..dfbce8b5 100644 --- a/lib/resource_registry/entities/registry.rb +++ b/lib/resource_registry/entities/registry.rb @@ -1,8 +1,14 @@ +require 'resource_registry/entities/option' + module ResourceRegistry module Entities class Registry < Dry::Struct # Configuration values + # Only let this be defined once, dry autoloading will + # try to continue loading this file and give us conflict + # issues. + unless defined?(Config) attribute :config do attribute :name, Types::Strict::String attribute :root, Types::Strict::String @@ -15,7 +21,7 @@ class Registry < Dry::Struct # Dir, plus optional custom auto_register block attribute :auto_register, Types::Array.of(Types::NilOrString) #| Types::Undefined end - + attribute :timestamp, Types::DateTime.default { DateTime.now } # Persistence values @@ -26,8 +32,8 @@ class Registry < Dry::Struct end # Override or additional attributes - attribute :options, ResourceRegistry::Entities::Option - + attribute :options, Types::Constructor(ResourceRegistry::Entities::Option) + end end end end diff --git a/lib/resource_registry/entities/setting.rb b/lib/resource_registry/entities/setting.rb index 2b3fdb73..1d14398f 100644 --- a/lib/resource_registry/entities/setting.rb +++ b/lib/resource_registry/entities/setting.rb @@ -1,16 +1,18 @@ +require 'resource_registry/entities/dry_struct_setters' + module ResourceRegistry module Entities - class Setting < Dry::Struct - include DryStructSetters - transform_keys(&:to_sym) + class Setting + extend Dry::Initializer - attribute :key, Types::Symbol - attribute :title?, Types::String - attribute :description?, Types::String - attribute :type?, Types::Symbol - attribute :default?, Types::Any - attribute :value?, Types::Any - + option :key, type: Dry::Types["coercible.symbol"] + option :title, type: Types::String, optional: true + option :description, type: Types::String, optional: true + option :type, type: Dry::Types["coercible.symbol"], optional: true + option :default, type: Types::Any, optional: true + option :value, type: Types::Any, optional: true end + + SettingConstructor = Types.Constructor(Setting) { |val| Setting.new(val) } end end \ No newline at end of file diff --git a/lib/resource_registry/options/validation/option_contract.rb b/lib/resource_registry/options/validation/option_contract.rb index ff368610..d0ce4f72 100644 --- a/lib/resource_registry/options/validation/option_contract.rb +++ b/lib/resource_registry/options/validation/option_contract.rb @@ -1,15 +1,47 @@ require 'dry/validation' -require 'resource_registry/registries/validation/registry_contract' +require File.expand_path(File.join(File.dirname(__FILE__), "setting_contract")) module ResourceRegistry module Options module Validation - class OptionContract < ResourceRegistry::Validation::ApplicationContract + unless defined?(MAX_OPTION_DEPTH) + MAX_OPTION_DEPTH = 15 + end + + class BottomNamespaceContract < ResourceRegistry::Validation::ApplicationContract + params do + required(:key).filled(Dry::Types["string"] | Dry::Types["symbol"]) + optional(:settings).array(SettingContract.__schema__) + end + end + + eval <<-RUBYCODE + class NamespaceLevel#{MAX_OPTION_DEPTH}Contract < ResourceRegistry::Validation::ApplicationContract + params do + required(:key).filled(Dry::Types["string"] | Dry::Types["symbol"]) + optional(:settings).array(SettingContract.__schema__) + optional(:namespaces).array(BottomNamespaceContract.__schema__) + end + end + RUBYCODE + + (1..(MAX_OPTION_DEPTH - 1)).to_a.reverse.each do |i| + eval(<<-RUBYCODE) + class NamespaceLevel#{i}Contract < ResourceRegistry::Validation::ApplicationContract + params do + required(:key).filled(Dry::Types["string"] | Dry::Types["symbol"]) + optional(:settings).array(SettingContract.__schema__) + optional(:namespaces).array(NamespaceLevel#{i + 1}Contract.__schema__) + end + end + RUBYCODE + end + class OptionContract < ResourceRegistry::Validation::ApplicationContract params do - required(:key).filled(:string) - optional(:settings).array(type?: SettingContract) - optional(:namespaces).array(type?: OptionContract) + required(:key).filled(Dry::Types["string"] | Dry::Types["symbol"]) + optional(:settings).array(SettingContract.__schema__) + optional(:namespaces).array(NamespaceLevel1Contract.__schema__) end end end diff --git a/lib/resource_registry/options/validation/option_schema.rb b/lib/resource_registry/options/validation/option_schema.rb deleted file mode 100644 index 3618d0c1..00000000 --- a/lib/resource_registry/options/validation/option_schema.rb +++ /dev/null @@ -1,14 +0,0 @@ -module ResourceRegistry - module Options - module Validation - class OptionSchema < ResourceRegistry::Validation::ApplicationSchema - - define do - required(:key).filled(:symbol) - optional(:settings).array(type?: SettingSchema) - optional(:namespaces).array(type?: OptionSchema) - end - end - end - end -end \ No newline at end of file diff --git a/lib/resource_registry/options/validation/setting_contract.rb b/lib/resource_registry/options/validation/setting_contract.rb index c5149b74..9e34b199 100644 --- a/lib/resource_registry/options/validation/setting_contract.rb +++ b/lib/resource_registry/options/validation/setting_contract.rb @@ -7,12 +7,12 @@ module Validation class SettingContract < ResourceRegistry::Validation::ApplicationContract params do - required(:key).filled(:symbol) - required(:default).filled(:string) + required(:key).filled(Dry::Types["string"] | Dry::Types["symbol"]) + required(:default).filled(:any) optional(:title).filled(:string) optional(:description).filled(:string) - optional(:type).filled(:string) - optional(:value).filled(:string) + optional(:type).filled(Dry::Types["string"] | Dry::Types["symbol"]) + optional(:value).filled(:any) end end diff --git a/lib/resource_registry/options/validation/setting_schema.rb b/lib/resource_registry/options/validation/setting_schema.rb deleted file mode 100644 index 2922a87a..00000000 --- a/lib/resource_registry/options/validation/setting_schema.rb +++ /dev/null @@ -1,17 +0,0 @@ -module ResourceRegistry - module Options - module Validation - class SettingSchema < ResourceRegistry::Validation::ApplicationSchema - - define do - required(:key).filled(:symbol) - required(:default).filled(:any) - optional(:title).filled(:string) - optional(:description).filled(:string) - optional(:type).filled(:string) - optional(:value).filled(:any) - end - end - end - end -end diff --git a/lib/resource_registry/serializers/operations/generate_option.rb b/lib/resource_registry/serializers/operations/generate_option.rb index 7c58ba10..88e55604 100644 --- a/lib/resource_registry/serializers/operations/generate_option.rb +++ b/lib/resource_registry/serializers/operations/generate_option.rb @@ -1,42 +1,21 @@ +require File.expand_path(File.join(File.dirname(__FILE__), "../..", "options/validation/option_contract")) + module ResourceRegistry module Serializers module Operations class GenerateOption - include Dry::Transaction::Operation def call(input) - option = convert(result: input) - return Success(option) - end - - private - - # def convert(input: nil) - # input.symbolize_keys! - - # ResourceRegistry::Entities::Option.new(input) - # end - - def convert(result: nil, parent_node: nil) - result.symbolize_keys! - if result.keys.include?(:namespaces) || result.keys.include?(:settings) - options = ResourceRegistry::Entities::Option.new(key: result[:key].to_sym) - root = options if parent_node.blank? - parent_node.namespaces = parent_node.namespaces.to_a + [options] if parent_node.present? - [:namespaces, :settings].each do |attrs| - result[attrs].each {|element| convert(result: element, parent_node: options) } if result[attrs].present? - end + schema = schema = ResourceRegistry::Options::Validation::OptionContract.new + result = schema.call(input) + + if result.success? + option = ResourceRegistry::Entities::Option.new(result.to_h) + Success(option) else - result.tap do |attrs| - attrs[:key] = attrs[:key].to_sym - attrs[:default] = attrs[:default] - attrs[:value] = attrs[:value] if attrs[:value].present? - end - setting = ResourceRegistry::Entities::Setting.new(result) - parent_node.settings = parent_node.settings.to_a + [setting] + Failure(result.errors) end - root end end end diff --git a/lib/resource_registry/serializers/operations/parse_yaml.rb b/lib/resource_registry/serializers/operations/parse_yaml.rb index 3e1d3ee1..eb982e94 100644 --- a/lib/resource_registry/serializers/operations/parse_yaml.rb +++ b/lib/resource_registry/serializers/operations/parse_yaml.rb @@ -8,7 +8,7 @@ class ParseYaml include Dry::Transaction::Operation def call(input) - return Success(YAML.load(input)) + Success(YAML.load(input)) end end end diff --git a/lib/resource_registry/serializers/option_generate.rb b/lib/resource_registry/serializers/option_generate.rb index a04cfa75..48aa4e0b 100644 --- a/lib/resource_registry/serializers/option_generate.rb +++ b/lib/resource_registry/serializers/option_generate.rb @@ -4,93 +4,19 @@ class OptionGenerate include ResourceRegistry::Services::Service # include ResourceRegistry::PrivateInject["option_hash.validate"] - def call(**params) + def call(params) execute(params) end def execute(option_hash) - - parse_hash(option_hash) - result = option_hash.validate - + schema = ResourceRegistry::Options::Validation::OptionContract.new + result = schema.call(option_hash) if result.success? - parse_hash(option_hash) + ResourceRegistry::Entities::Option.new(result.to_h) else - # raise InvalidOptionHash if @content['namespace'].blank? raise InvalidOptionHash, "result.errors" end end - - - private - - def convert(result: nil, parent_node: nil) - result.symbolize_keys! - - if result.keys.include?(:namespaces) || result.keys.include?(:settings) - options = ResourceRegistry::Entities::Option.new(key: result[:key].to_sym) - root = options if parent_node.blank? - parent_node.namespaces = parent_node.namespaces.to_a + [options] if parent_node.present? - [:namespaces, :settings].each do |attrs| - result[attrs].each {|element| convert(result: element, parent_node: options) } if result[attrs].present? - end - else - result.tap do |attrs| - attrs[:key] = attrs[:key].to_sym - attrs[:default] = attrs[:default].to_s - attrs[:value] = attrs[:value].to_s if attrs[:value].present? - end - setting = ResourceRegistry::Entities::Setting.new(result) - parent_node.settings = parent_node.settings.to_a + [setting] - end - - root - end - - def parse_hash(hash_tree, ns_stack = [], namespaces = [], settings = []) - option_list = [] - - hash_tree.each_pair do |key, nodes| - case key - when :namespace - ns_stack = [] - ns_stack.push key.to_s - nodes.delete(:key) - - when :namespaces - option_list << nodes.reduce([]) do |list, node| - ns_stack.push key.to_s - node.delete(:key) - node.reduce([]) do |list, child_node| - parse_hash(child_node, ns_stack, namespaces, settings) - hash_tree.delete(key) - end -binding.pry - full_namespace = ns_stack.join('.') - list << ResourceRegistry::Entities::Option.new(key: full_namespace, namespaces: namespaces, settings: settings) -binding.pry - nodes.delete(:key) - settings = [] - namespaces = [] - end - -binding.pry - when :settings - nodes.reduce([]) do | list, setting_hash | - list << ResourceRegistry::Entities::Setting.new(setting_hash) - end - - # hash_tree.delete(:settings) - # parse_hash(hash_tree, ns_stack, namespaces, settings) - end - - option_list - - end - end - - - end end end \ No newline at end of file diff --git a/spec/resource_registry/serializers/operations/generate_container_spec.rb b/spec/resource_registry/serializers/operations/generate_container_spec.rb index 41cfff41..07ae254a 100644 --- a/spec/resource_registry/serializers/operations/generate_container_spec.rb +++ b/spec/resource_registry/serializers/operations/generate_container_spec.rb @@ -8,7 +8,7 @@ context 'When valid option passed' do let(:input) { - ResourceRegistry::Entities::Option.new(options_hash) + ResourceRegistry::Serializers::OptionGenerate.new.execute(options_hash) } let(:expected_keys) { diff --git a/spec/resource_registry_spec.rb b/spec/resource_registry_spec.rb index 00e60be0..3a2560c8 100644 --- a/spec/resource_registry_spec.rb +++ b/spec/resource_registry_spec.rb @@ -71,19 +71,22 @@ context ".load_options" do before(:all) do - ResourceRegistry.load_options!(option_files_dir) + @result = ResourceRegistry.load_options!(option_files_dir) + end + + it "loads successfully" do + expect(@result.success?).to be_truthy end it 'should load site options' do expect(Registry.keys.include?("tenants.dchbx.applications.enroll.features")).to be_truthy - expect(Registry.keys.include?("tenants.dchbx.applications.edi.features")).to be_truthy end - it 'should load shop market options' do + it 'should load individual market options' do expect(Registry.keys.any?{|key| key.scan(/tenants.dchbx.applications.enroll.features.individual_market/).present?}).to be_truthy end - it 'should load individual market options' do + it 'should load shop market options' do expect(Registry.keys.any?{|key| key.scan("tenants.dchbx.applications.enroll.features.aca_shop_market").present?}).to be_truthy end end