ama-entity-mapper 0.1.0.beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/docs/algorithm.md +61 -0
- data/docs/basic-usage.md +179 -0
- data/docs/dsl.md +90 -0
- data/docs/generics.md +55 -0
- data/docs/handlers.md +196 -0
- data/docs/index.md +23 -0
- data/docs/installation.md +27 -0
- data/docs/logging.md +18 -0
- data/docs/wildcards.md +16 -0
- data/lib/ama-entity-mapper.rb +42 -0
- data/lib/ama-entity-mapper/aux/null_stream.rb +30 -0
- data/lib/ama-entity-mapper/context.rb +61 -0
- data/lib/ama-entity-mapper/dsl.rb +21 -0
- data/lib/ama-entity-mapper/dsl/class_methods.rb +100 -0
- data/lib/ama-entity-mapper/engine.rb +88 -0
- data/lib/ama-entity-mapper/engine/recursive_mapper.rb +164 -0
- data/lib/ama-entity-mapper/engine/recursive_normalizer.rb +74 -0
- data/lib/ama-entity-mapper/error.rb +11 -0
- data/lib/ama-entity-mapper/error/compliance_error.rb +15 -0
- data/lib/ama-entity-mapper/error/mapping_error.rb +14 -0
- data/lib/ama-entity-mapper/error/validation_error.rb +14 -0
- data/lib/ama-entity-mapper/handler/attribute/validator.rb +107 -0
- data/lib/ama-entity-mapper/handler/entity/denormalizer.rb +97 -0
- data/lib/ama-entity-mapper/handler/entity/enumerator.rb +76 -0
- data/lib/ama-entity-mapper/handler/entity/factory.rb +86 -0
- data/lib/ama-entity-mapper/handler/entity/injector.rb +69 -0
- data/lib/ama-entity-mapper/handler/entity/normalizer.rb +68 -0
- data/lib/ama-entity-mapper/handler/entity/validator.rb +66 -0
- data/lib/ama-entity-mapper/mixin/errors.rb +55 -0
- data/lib/ama-entity-mapper/mixin/handler_support.rb +69 -0
- data/lib/ama-entity-mapper/mixin/reflection.rb +67 -0
- data/lib/ama-entity-mapper/mixin/suppression_support.rb +37 -0
- data/lib/ama-entity-mapper/path.rb +91 -0
- data/lib/ama-entity-mapper/path/segment.rb +51 -0
- data/lib/ama-entity-mapper/type.rb +243 -0
- data/lib/ama-entity-mapper/type/analyzer.rb +27 -0
- data/lib/ama-entity-mapper/type/any.rb +66 -0
- data/lib/ama-entity-mapper/type/attribute.rb +197 -0
- data/lib/ama-entity-mapper/type/aux/hash_tuple.rb +35 -0
- data/lib/ama-entity-mapper/type/builtin/array_type.rb +28 -0
- data/lib/ama-entity-mapper/type/builtin/datetime_type.rb +65 -0
- data/lib/ama-entity-mapper/type/builtin/enumerable_type.rb +74 -0
- data/lib/ama-entity-mapper/type/builtin/hash_tuple_type.rb +33 -0
- data/lib/ama-entity-mapper/type/builtin/hash_type.rb +82 -0
- data/lib/ama-entity-mapper/type/builtin/primitive_type.rb +61 -0
- data/lib/ama-entity-mapper/type/builtin/primitive_type/denormalizer.rb +62 -0
- data/lib/ama-entity-mapper/type/builtin/rational_type.rb +59 -0
- data/lib/ama-entity-mapper/type/builtin/set_type.rb +74 -0
- data/lib/ama-entity-mapper/type/parameter.rb +70 -0
- data/lib/ama-entity-mapper/type/registry.rb +117 -0
- data/lib/ama-entity-mapper/type/resolver.rb +105 -0
- data/lib/ama-entity-mapper/version.rb +17 -0
- metadata +194 -0
| @@ -0,0 +1,35 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module AMA
         | 
| 4 | 
            +
              module Entity
         | 
| 5 | 
            +
                class Mapper
         | 
| 6 | 
            +
                  class Type
         | 
| 7 | 
            +
                    module Aux
         | 
| 8 | 
            +
                      # Simple class to store paired data items
         | 
| 9 | 
            +
                      class HashTuple
         | 
| 10 | 
            +
                        attr_accessor :key
         | 
| 11 | 
            +
                        attr_accessor :value
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                        def initialize(key: nil, value: nil)
         | 
| 14 | 
            +
                          @key = key
         | 
| 15 | 
            +
                          @value = value
         | 
| 16 | 
            +
                        end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                        def hash
         | 
| 19 | 
            +
                          @key.hash ^ @value.hash
         | 
| 20 | 
            +
                        end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                        def eql?(other)
         | 
| 23 | 
            +
                          return false unless other.is_a?(HashTuple)
         | 
| 24 | 
            +
                          @key == other.key && @value == other.value
         | 
| 25 | 
            +
                        end
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                        def ==(other)
         | 
| 28 | 
            +
                          eql?(other)
         | 
| 29 | 
            +
                        end
         | 
| 30 | 
            +
                      end
         | 
| 31 | 
            +
                    end
         | 
| 32 | 
            +
                  end
         | 
| 33 | 
            +
                end
         | 
| 34 | 
            +
              end
         | 
| 35 | 
            +
            end
         | 
| @@ -0,0 +1,28 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require_relative 'enumerable_type'
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            module AMA
         | 
| 6 | 
            +
              module Entity
         | 
| 7 | 
            +
                class Mapper
         | 
| 8 | 
            +
                  class Type
         | 
| 9 | 
            +
                    module BuiltIn
         | 
| 10 | 
            +
                      # Even though it is functionally unnecessary, end users are more
         | 
| 11 | 
            +
                      # likely to call `Mapper.map(input, Array)` rather than
         | 
| 12 | 
            +
                      # `Mapper.map(input, Enumerable)`. Because mapper has no right to
         | 
| 13 | 
            +
                      # make assumptions about type children, it would have to back off to
         | 
| 14 | 
            +
                      # standard hash-based normalization/denormalization, and that would
         | 
| 15 | 
            +
                      # cause end-user frustration
         | 
| 16 | 
            +
                      class ArrayType < EnumerableType
         | 
| 17 | 
            +
                        def initialize
         | 
| 18 | 
            +
                          super
         | 
| 19 | 
            +
                          self.type = Array
         | 
| 20 | 
            +
                        end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                        INSTANCE = new
         | 
| 23 | 
            +
                      end
         | 
| 24 | 
            +
                    end
         | 
| 25 | 
            +
                  end
         | 
| 26 | 
            +
                end
         | 
| 27 | 
            +
              end
         | 
| 28 | 
            +
            end
         | 
| @@ -0,0 +1,65 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require 'date'
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            require_relative '../../type'
         | 
| 6 | 
            +
            require_relative '../../mixin/errors'
         | 
| 7 | 
            +
             | 
| 8 | 
            +
            module AMA
         | 
| 9 | 
            +
              module Entity
         | 
| 10 | 
            +
                class Mapper
         | 
| 11 | 
            +
                  class Type
         | 
| 12 | 
            +
                    module BuiltIn
         | 
| 13 | 
            +
                      # DateTime type description
         | 
| 14 | 
            +
                      class DateTimeType < Type
         | 
| 15 | 
            +
                        def initialize
         | 
| 16 | 
            +
                          super(DateTime)
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                          normalizer_block do |entity, *|
         | 
| 19 | 
            +
                            entity.iso8601(3)
         | 
| 20 | 
            +
                          end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                          define_denormalizer
         | 
| 23 | 
            +
                          define_factory
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                          enumerator_block do |*|
         | 
| 26 | 
            +
                            ::Enumerator.new { |*| }
         | 
| 27 | 
            +
                          end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                          injector_block { |*| }
         | 
| 30 | 
            +
                        end
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                        private
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                        def define_denormalizer
         | 
| 35 | 
            +
                          denormalizer_block do |input, _, ctx|
         | 
| 36 | 
            +
                            break input if input.is_a?(DateTime)
         | 
| 37 | 
            +
                            input = input.to_s if input.is_a?(Symbol)
         | 
| 38 | 
            +
                            break DateTime.iso8601(input, 3) if input.is_a?(String)
         | 
| 39 | 
            +
                            if input.is_a?(Integer)
         | 
| 40 | 
            +
                              break DateTime.strptime(input.to_s, '%s')
         | 
| 41 | 
            +
                            end
         | 
| 42 | 
            +
                            singleton_class.send(:include, Mixin::Errors)
         | 
| 43 | 
            +
                            message = 'String input expected (like ' \
         | 
| 44 | 
            +
                              "'2001-02-03T04:05:06.123+04:00'), " \
         | 
| 45 | 
            +
                              "#{input.class} received: #{input}"
         | 
| 46 | 
            +
                            mapping_error(message, context: ctx)
         | 
| 47 | 
            +
                          end
         | 
| 48 | 
            +
                        end
         | 
| 49 | 
            +
             | 
| 50 | 
            +
                        def define_factory
         | 
| 51 | 
            +
                          factory_block do |_, _, ctx|
         | 
| 52 | 
            +
                            singleton_class.send(:include, Mixin::Errors)
         | 
| 53 | 
            +
                            message = 'DateTime type could not be instantiated directly, ' \
         | 
| 54 | 
            +
                              'it only supports normalization and denormalization'
         | 
| 55 | 
            +
                            compliance_error(message, context: ctx)
         | 
| 56 | 
            +
                          end
         | 
| 57 | 
            +
                        end
         | 
| 58 | 
            +
             | 
| 59 | 
            +
                        INSTANCE = new
         | 
| 60 | 
            +
                      end
         | 
| 61 | 
            +
                    end
         | 
| 62 | 
            +
                  end
         | 
| 63 | 
            +
                end
         | 
| 64 | 
            +
              end
         | 
| 65 | 
            +
            end
         | 
| @@ -0,0 +1,74 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require_relative '../../type'
         | 
| 4 | 
            +
            require_relative '../../path/segment'
         | 
| 5 | 
            +
            require_relative '../../mixin/errors'
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            module AMA
         | 
| 8 | 
            +
              module Entity
         | 
| 9 | 
            +
                class Mapper
         | 
| 10 | 
            +
                  class Type
         | 
| 11 | 
            +
                    module BuiltIn
         | 
| 12 | 
            +
                      # Default Enumerable handler
         | 
| 13 | 
            +
                      class EnumerableType < Type
         | 
| 14 | 
            +
                        include Mixin::Errors
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                        def initialize
         | 
| 17 | 
            +
                          super(::Enumerable)
         | 
| 18 | 
            +
                          attribute!(:_value, parameter!(:T), virtual: true)
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                          define_factory
         | 
| 21 | 
            +
                          define_normalizer
         | 
| 22 | 
            +
                          define_denormalizer
         | 
| 23 | 
            +
                          define_enumerator
         | 
| 24 | 
            +
                          define_injector
         | 
| 25 | 
            +
                        end
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                        private
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                        def define_factory
         | 
| 30 | 
            +
                          factory_block do |*|
         | 
| 31 | 
            +
                            []
         | 
| 32 | 
            +
                          end
         | 
| 33 | 
            +
                        end
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                        def define_normalizer
         | 
| 36 | 
            +
                          normalizer_block do |input, *|
         | 
| 37 | 
            +
                            input.map(&:itself)
         | 
| 38 | 
            +
                          end
         | 
| 39 | 
            +
                        end
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                        def define_denormalizer
         | 
| 42 | 
            +
                          denormalizer_block do |data, type, context = nil, *|
         | 
| 43 | 
            +
                            if data.is_a?(Hash) || !data.is_a?(Enumerable)
         | 
| 44 | 
            +
                              message = "Can't denormalize Enumerable from #{data.class}"
         | 
| 45 | 
            +
                              type.mapping_error(message, context: context)
         | 
| 46 | 
            +
                            end
         | 
| 47 | 
            +
                            data.map(&:itself)
         | 
| 48 | 
            +
                          end
         | 
| 49 | 
            +
                        end
         | 
| 50 | 
            +
             | 
| 51 | 
            +
                        def define_enumerator
         | 
| 52 | 
            +
                          enumerator_block do |entity, type, *|
         | 
| 53 | 
            +
                            ::Enumerator.new do |yielder|
         | 
| 54 | 
            +
                              attribute = type.attributes[:_value]
         | 
| 55 | 
            +
                              entity.each_with_index do |value, index|
         | 
| 56 | 
            +
                                yielder << [attribute, value, Path::Segment.index(index)]
         | 
| 57 | 
            +
                              end
         | 
| 58 | 
            +
                            end
         | 
| 59 | 
            +
                          end
         | 
| 60 | 
            +
                        end
         | 
| 61 | 
            +
             | 
| 62 | 
            +
                        def define_injector
         | 
| 63 | 
            +
                          injector_block do |entity, _, _, value, context|
         | 
| 64 | 
            +
                            entity[context.path.current.name] = value
         | 
| 65 | 
            +
                          end
         | 
| 66 | 
            +
                        end
         | 
| 67 | 
            +
             | 
| 68 | 
            +
                        INSTANCE = new
         | 
| 69 | 
            +
                      end
         | 
| 70 | 
            +
                    end
         | 
| 71 | 
            +
                  end
         | 
| 72 | 
            +
                end
         | 
| 73 | 
            +
              end
         | 
| 74 | 
            +
            end
         | 
| @@ -0,0 +1,33 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require_relative '../../type'
         | 
| 4 | 
            +
            require_relative '../aux/hash_tuple'
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            module AMA
         | 
| 7 | 
            +
              module Entity
         | 
| 8 | 
            +
                class Mapper
         | 
| 9 | 
            +
                  class Type
         | 
| 10 | 
            +
                    module BuiltIn
         | 
| 11 | 
            +
                      # Pair class definition
         | 
| 12 | 
            +
                      class HashTupleType < Type
         | 
| 13 | 
            +
                        def initialize
         | 
| 14 | 
            +
                          super(Aux::HashTuple, virtual: true)
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                          attribute!(:key, parameter!(:K))
         | 
| 17 | 
            +
                          attribute!(:value, parameter!(:V))
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                          enumerator_block do |entity, type, *|
         | 
| 20 | 
            +
                            ::Enumerator.new do |yielder|
         | 
| 21 | 
            +
                              yielder << [type.attributes[:key], entity.key, nil]
         | 
| 22 | 
            +
                              yielder << [type.attributes[:value], entity.value, nil]
         | 
| 23 | 
            +
                            end
         | 
| 24 | 
            +
                          end
         | 
| 25 | 
            +
                        end
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                        INSTANCE = new
         | 
| 28 | 
            +
                      end
         | 
| 29 | 
            +
                    end
         | 
| 30 | 
            +
                  end
         | 
| 31 | 
            +
                end
         | 
| 32 | 
            +
              end
         | 
| 33 | 
            +
            end
         | 
| @@ -0,0 +1,82 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require_relative '../../type'
         | 
| 4 | 
            +
            require_relative '../../path/segment'
         | 
| 5 | 
            +
            require_relative '../../mixin/errors'
         | 
| 6 | 
            +
            require_relative '../../mixin/reflection'
         | 
| 7 | 
            +
            require_relative 'hash_tuple_type'
         | 
| 8 | 
            +
            require_relative '../aux/hash_tuple'
         | 
| 9 | 
            +
             | 
| 10 | 
            +
            module AMA
         | 
| 11 | 
            +
              module Entity
         | 
| 12 | 
            +
                class Mapper
         | 
| 13 | 
            +
                  class Type
         | 
| 14 | 
            +
                    module BuiltIn
         | 
| 15 | 
            +
                      # Predefined type for Hash class
         | 
| 16 | 
            +
                      class HashType < Type
         | 
| 17 | 
            +
                        include Mixin::Errors
         | 
| 18 | 
            +
                        extend Mixin::Errors
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                        def initialize
         | 
| 21 | 
            +
                          super(::Hash)
         | 
| 22 | 
            +
                          define_attribute
         | 
| 23 | 
            +
                          define_enumerator
         | 
| 24 | 
            +
                          define_injector
         | 
| 25 | 
            +
                          define_normalizer
         | 
| 26 | 
            +
                          define_denormalizer
         | 
| 27 | 
            +
                        end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                        private
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                        def define_attribute
         | 
| 32 | 
            +
                          type = HashTupleType.new
         | 
| 33 | 
            +
                          type = type.resolve(
         | 
| 34 | 
            +
                            type.parameter!(:K) => parameter!(:K),
         | 
| 35 | 
            +
                            type.parameter!(:V) => parameter!(:V)
         | 
| 36 | 
            +
                          )
         | 
| 37 | 
            +
                          attribute!(:_tuple, type, virtual: true)
         | 
| 38 | 
            +
                        end
         | 
| 39 | 
            +
             | 
| 40 | 
            +
                        def define_enumerator
         | 
| 41 | 
            +
                          enumerator_block do |entity, type, *|
         | 
| 42 | 
            +
                            ::Enumerator.new do |yielder|
         | 
| 43 | 
            +
                              entity.each do |key, value|
         | 
| 44 | 
            +
                                tuple = Aux::HashTuple.new(key: key, value: value)
         | 
| 45 | 
            +
                                attribute = type.attributes[:_tuple]
         | 
| 46 | 
            +
                                yielder << [attribute, tuple, Path::Segment.index(key)]
         | 
| 47 | 
            +
                              end
         | 
| 48 | 
            +
                            end
         | 
| 49 | 
            +
                          end
         | 
| 50 | 
            +
                        end
         | 
| 51 | 
            +
             | 
| 52 | 
            +
                        def define_injector
         | 
| 53 | 
            +
                          injector_block do |entity, _, _, tuple, *|
         | 
| 54 | 
            +
                            entity[tuple.key] = tuple.value
         | 
| 55 | 
            +
                          end
         | 
| 56 | 
            +
                        end
         | 
| 57 | 
            +
             | 
| 58 | 
            +
                        def define_denormalizer
         | 
| 59 | 
            +
                          denormalizer_block do |input, type, context = nil, *|
         | 
| 60 | 
            +
                            input = input.to_h if input.respond_to?(:to_h)
         | 
| 61 | 
            +
                            break input if input.is_a?(Hash)
         | 
| 62 | 
            +
                            message = "Expected to receive hash, #{input.class} received"
         | 
| 63 | 
            +
                            type.mapping_error(message, context: context)
         | 
| 64 | 
            +
                          end
         | 
| 65 | 
            +
                        end
         | 
| 66 | 
            +
             | 
| 67 | 
            +
                        def define_normalizer
         | 
| 68 | 
            +
                          normalizer_block do |entity, *|
         | 
| 69 | 
            +
                            intermediate = entity.map do |key, value|
         | 
| 70 | 
            +
                              [key, value]
         | 
| 71 | 
            +
                            end
         | 
| 72 | 
            +
                            Hash[intermediate]
         | 
| 73 | 
            +
                          end
         | 
| 74 | 
            +
                        end
         | 
| 75 | 
            +
             | 
| 76 | 
            +
                        INSTANCE = new
         | 
| 77 | 
            +
                      end
         | 
| 78 | 
            +
                    end
         | 
| 79 | 
            +
                  end
         | 
| 80 | 
            +
                end
         | 
| 81 | 
            +
              end
         | 
| 82 | 
            +
            end
         | 
| @@ -0,0 +1,61 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require_relative '../../type'
         | 
| 4 | 
            +
            require_relative '../../mixin/errors'
         | 
| 5 | 
            +
            require_relative 'primitive_type/denormalizer'
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            module AMA
         | 
| 8 | 
            +
              module Entity
         | 
| 9 | 
            +
                class Mapper
         | 
| 10 | 
            +
                  class Type
         | 
| 11 | 
            +
                    module BuiltIn
         | 
| 12 | 
            +
                      # Predefined type for Set class
         | 
| 13 | 
            +
                      class PrimitiveType < Type
         | 
| 14 | 
            +
                        include Mixin::Errors
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                        def initialize(type, method_map)
         | 
| 17 | 
            +
                          super(type)
         | 
| 18 | 
            +
                          this = self
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                          factory_block do |*|
         | 
| 21 | 
            +
                            this.compliance_error("#{this} factory should never be called")
         | 
| 22 | 
            +
                          end
         | 
| 23 | 
            +
                          normalizer_block do |entity, *|
         | 
| 24 | 
            +
                            entity
         | 
| 25 | 
            +
                          end
         | 
| 26 | 
            +
                          self.denormalizer = Denormalizer.new(method_map)
         | 
| 27 | 
            +
                          enumerator_block do |*|
         | 
| 28 | 
            +
                            Enumerator.new { |*| }
         | 
| 29 | 
            +
                          end
         | 
| 30 | 
            +
                          injector_block { |*| }
         | 
| 31 | 
            +
                        end
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                        # This hash describes which helper methods may be used for which
         | 
| 34 | 
            +
                        # type to extract target primitive. During the run inheritance chain
         | 
| 35 | 
            +
                        # is unwrapped, and first matching entry (topmost one) is used.
         | 
| 36 | 
            +
                        primitives = {
         | 
| 37 | 
            +
                          Symbol => { Object => [], String => %i[to_sym] },
         | 
| 38 | 
            +
                          String => { Object => [], Symbol => %i[to_s] },
         | 
| 39 | 
            +
                          Numeric => { Object => %i[to_i to_f], String => [] },
         | 
| 40 | 
            +
                          Integer => { Object => %i[to_i], String => [] },
         | 
| 41 | 
            +
                          Float => { Object => %i[to_f], String => [] },
         | 
| 42 | 
            +
                          TrueClass => { Object => %i[to_b to_bool], String => [] },
         | 
| 43 | 
            +
                          FalseClass => { Object => %i[to_b to_bool], String => [] },
         | 
| 44 | 
            +
                          NilClass => { Object => [] }
         | 
| 45 | 
            +
                        }
         | 
| 46 | 
            +
             | 
| 47 | 
            +
                        # rubocop:disable Lint/UnifiedInteger
         | 
| 48 | 
            +
                        if defined?(Fixnum)
         | 
| 49 | 
            +
                          primitives[Fixnum] = { Object => %i[to_i], String => [] }
         | 
| 50 | 
            +
                        end
         | 
| 51 | 
            +
                        # rubocop:enable Lint/UnifiedInteger
         | 
| 52 | 
            +
             | 
| 53 | 
            +
                        ALL = primitives.map do |klass, method_map|
         | 
| 54 | 
            +
                          const_set(klass.to_s.upcase, new(klass, method_map))
         | 
| 55 | 
            +
                        end
         | 
| 56 | 
            +
                      end
         | 
| 57 | 
            +
                    end
         | 
| 58 | 
            +
                  end
         | 
| 59 | 
            +
                end
         | 
| 60 | 
            +
              end
         | 
| 61 | 
            +
            end
         | 
| @@ -0,0 +1,62 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require_relative '../../../type'
         | 
| 4 | 
            +
            require_relative '../../../mixin/errors'
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            module AMA
         | 
| 7 | 
            +
              module Entity
         | 
| 8 | 
            +
                class Mapper
         | 
| 9 | 
            +
                  class Type
         | 
| 10 | 
            +
                    module BuiltIn
         | 
| 11 | 
            +
                      class PrimitiveType < Type
         | 
| 12 | 
            +
                        # Standard denormalizer for primitive type
         | 
| 13 | 
            +
                        class Denormalizer
         | 
| 14 | 
            +
                          include Mixin::Errors
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                          # @param [Hash{Class, Array<Symbol>}] method_map
         | 
| 17 | 
            +
                          def initialize(method_map)
         | 
| 18 | 
            +
                            @method_map = method_map
         | 
| 19 | 
            +
                          end
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                          # @param [Object] source
         | 
| 22 | 
            +
                          # @param [AMA::Entity::Mapper::Type] type
         | 
| 23 | 
            +
                          # @param [AMA::Entity::Mapper::Context] context
         | 
| 24 | 
            +
                          def denormalize(source, type, context)
         | 
| 25 | 
            +
                            return source if type.valid?(source, context)
         | 
| 26 | 
            +
                            find_candidate_methods(source.class).each do |candidate|
         | 
| 27 | 
            +
                              begin
         | 
| 28 | 
            +
                                next unless source.respond_to?(candidate)
         | 
| 29 | 
            +
                                value = source.send(candidate)
         | 
| 30 | 
            +
                                return value if type.valid?(value, context)
         | 
| 31 | 
            +
                              rescue StandardError => e
         | 
| 32 | 
            +
                                message = "Method #{candidate} failed with error when " \
         | 
| 33 | 
            +
                                  "denormalizing #{type.type} out of #{source.class}: " \
         | 
| 34 | 
            +
                                  "#{e.message}"
         | 
| 35 | 
            +
                                context.logger.warn(message)
         | 
| 36 | 
            +
                              end
         | 
| 37 | 
            +
                            end
         | 
| 38 | 
            +
                            message = "Can't create #{type} instance from #{source.class}"
         | 
| 39 | 
            +
                            mapping_error(message, context: context)
         | 
| 40 | 
            +
                          end
         | 
| 41 | 
            +
             | 
| 42 | 
            +
                          private
         | 
| 43 | 
            +
             | 
| 44 | 
            +
                          def find_candidate_methods(klass)
         | 
| 45 | 
            +
                            chain = []
         | 
| 46 | 
            +
                            cursor = klass
         | 
| 47 | 
            +
                            until cursor.nil?
         | 
| 48 | 
            +
                              chain.push(cursor)
         | 
| 49 | 
            +
                              cursor = cursor.superclass
         | 
| 50 | 
            +
                            end
         | 
| 51 | 
            +
                            winner = chain.find do |entry|
         | 
| 52 | 
            +
                              @method_map.key?(entry)
         | 
| 53 | 
            +
                            end
         | 
| 54 | 
            +
                            winner.nil? ? [] : @method_map[winner]
         | 
| 55 | 
            +
                          end
         | 
| 56 | 
            +
                        end
         | 
| 57 | 
            +
                      end
         | 
| 58 | 
            +
                    end
         | 
| 59 | 
            +
                  end
         | 
| 60 | 
            +
                end
         | 
| 61 | 
            +
              end
         | 
| 62 | 
            +
            end
         |