Completed
Push — master ( 308a2c...1aebdd )
by Fike
01:00
created

Attribute.resolved?   A

Complexity

Conditions 1

Size

Total Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 3
rs 10
cc 1
1
# frozen_string_literal: true
2
3
# rubocop:disable Metrics/ClassLength
4
5
require_relative '../handler/attribute/validator'
6
require_relative '../mixin/errors'
7
require_relative '../mixin/reflection'
8
require_relative '../mixin/handler_support'
9
require_relative 'parameter'
10
11
module AMA
12
  module Entity
13
    class Mapper
14
      class Type
15
        # Stores data about single type attribute
16
        class Attribute
17
          include Mixin::Errors
18
          include Mixin::Reflection
19
          include Mixin::HandlerSupport
20
21
          # @!attribute
22
          #   @return [AMA::Entity::Mapper::Type]
23
          attr_accessor :owner
24
          # @!attribute
25
          #   @return [Symbol]
26
          attr_accessor :name
27
          # @!attribute types List of possible types attribute may take
28
          #   @return [Array<AMA::Entity::Mapper::Type>]
29
          attr_accessor :types
30
          # If attribute is declared as virtual, it is omitted from all
31
          # automatic actions, such enumeration, normalization and
32
          # denormalization. Main motivation behind virtual attributes was
33
          # collections problem: collection can't be represented as hash of
34
          # attributes, however, virtual attribute may describe collection
35
          # content.
36
          #
37
          # @!attribute virtual
38
          #   @return [TrueClass, FalseClass]
39
          attr_accessor :virtual
40
          # If set to true, this attribute will be omitted during normalization
41
          # and won't be present in resulting structure.
42
          #
43
          # @!attribute sensitive
44
          #   @return [TrueClass, FalseClass]
45
          attr_accessor :sensitive
46
          # Default value that is set on automatic object creation.
47
          #
48
          # @!attribute default
49
          #   @return [Object]
50
          attr_accessor :default
51
          # Whether or not this attribute may be represented by null.
52
          #
53
          # @!attribute nullable
54
          #   @return [TrueClass, FalseClass]
55
          attr_accessor :nullable
56
          # List of values this attribute acceptable to take. Part of automatic
57
          # validation.
58
          #
59
          # @!attribute values
60
          #   @return [Array<Object>]
61
          attr_accessor :values
62
          # @!attribute aliases
63
          #   @return [Array<Symbol>]
64
          attr_accessor :aliases
65
66
          handler_namespace Handler::Attribute
67
68
          # Custom attribute validator
69
          #
70
          # @!attribute validator
71
          #   @return [API::AttributeValidator]
72
          handler :validator, :validate
73
74
          def self.defaults
75
            {
76
              virtual: false,
77
              sensitive: false,
78
              default: nil,
79
              nullable: false,
80
              values: [],
81
              validator: nil,
82
              aliases: []
83
            }
84 View Code Duplication
          end
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
85
86
          # @param [Mapper::Type] owner
87
          # @param [Symbol] name
88
          # @param [Array<Mapper::Type>] types
89
          # @param [Hash<Symbol, Object] options
90
          def initialize(owner, name, *types, **options)
91 View Code Duplication
            @owner = validate_owner!(owner)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
92
            @name = validate_name!(name)
93
            @types = validate_types!(types)
94
            self.class.defaults.each do |key, value|
95
              value = options.fetch(key, value)
96
              unless value.nil?
97
                set_object_attribute(self, key, options.fetch(key, value))
98
              end
99
            end
100
          end
101
102
          def violations(value, context)
103
            validator.validate(value, self, context)
104
          end
105
106
          def valid?(value, context)
107
            violations(value, context).empty?
108
          end
109
110
          def valid!(value, context)
111
            violations = self.violations(value, context)
112
            return if violations.empty?
113
            repr = violations.join(', ')
114
            message = "Attribute #{self} has failed validation: #{repr}"
115
            validation_error(message, context: context)
116
          end
117
118
          def resolved?
119
            types.all?(&:resolved?)
120
          end
121
122
          def resolved!(context = nil)
123
            types.each { |type| type.resolved!(context) }
124
          end
125
126
          # @param [AMA::Entity::Mapper::Type] parameter
127
          # @param [AMA::Entity::Mapper::Type] substitution
128
          # @return [AMA::Entity::Mapper::Type::Attribute]
129
          def resolve_parameter(parameter, substitution)
130
            clone.tap do |clone|
131
              clone.types = types.each_with_object([]) do |type, carrier|
132
                if type == parameter
133
                  buffer = substitution
134
                  buffer = [buffer] unless buffer.is_a?(Enumerable)
135
                  next carrier.push(*buffer)
136
                end
137
                carrier.push(type.resolve_parameter(parameter, substitution))
138
              end
139
            end
140
          end
141
142 View Code Duplication
          def hash
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
143
            @owner.hash ^ @name.hash
144
          end
145
146
          def eql?(other)
147
            return false unless other.is_a?(self.class)
148
            @owner == other.owner && @name == other.name
149
          end
150
151 View Code Duplication
          def ==(other)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
152
            eql?(other)
153
          end
154
155
          def to_def
156
            types = @types ? @types.map(&:to_def).join(', ') : 'none'
157
            message = "#{owner.type}.#{name}"
158 View Code Duplication
            message += ':virtual' if virtual
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
159
            "#{message}<#{types}>"
160
          end
161
162
          def to_s
163
            message = "Attribute #{owner.type}.#{name}"
164
            message = "#{message} (virtual)" if virtual
165
            types = @types ? @types.map(&:to_def).join(', ') : 'none'
166
            "#{message} <#{types}>"
167
          end
168
169
          private
170
171
          def validate_owner!(owner)
172
            return owner if owner.is_a?(Type)
173
            message = 'Provided owner has to be a Type instance,' \
174
              " #{owner.class} received"
175
            compliance_error(message)
176
          end
177
178
          def validate_name!(name)
179
            return name if name.is_a?(Symbol)
180
            message = "Provided name has to be Symbol, #{name.class} received"
181
            compliance_error(message)
182
          end
183
184
          def validate_types!(types)
185
            compliance_error("No types provided for #{self}") if types.empty?
186
            types.each do |type|
187
              next if type.is_a?(Type) || type.is_a?(Parameter)
188
              message = 'Provided type has to be a Type instance, ' \
189
                "#{type.class} received"
190
              compliance_error(message)
191
            end
192
          end
193
        end
194
      end
195
    end
196
  end
197
end
198