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,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AMA
|
4
|
+
module Entity
|
5
|
+
class Mapper
|
6
|
+
class Path
|
7
|
+
# Well, that's quite self-explanatory. Path consists of segments, and
|
8
|
+
# here's one.
|
9
|
+
class Segment
|
10
|
+
attr_reader :name
|
11
|
+
attr_reader :prefix
|
12
|
+
attr_reader :suffix
|
13
|
+
|
14
|
+
def initialize(name, prefix = nil, suffix = nil)
|
15
|
+
@name = name
|
16
|
+
@prefix = prefix
|
17
|
+
@suffix = suffix
|
18
|
+
end
|
19
|
+
|
20
|
+
def to_s
|
21
|
+
"#{@prefix}#{@name}#{@suffix}"
|
22
|
+
end
|
23
|
+
|
24
|
+
def hash
|
25
|
+
@name.hash ^ @prefix.hash ^ @suffix.hash
|
26
|
+
end
|
27
|
+
|
28
|
+
def eql?(other)
|
29
|
+
return false unless other.is_a?(self.class)
|
30
|
+
@name == other.name && @prefix == other.prefix &&
|
31
|
+
@suffix == other.suffix
|
32
|
+
end
|
33
|
+
|
34
|
+
def ==(other)
|
35
|
+
eql?(other)
|
36
|
+
end
|
37
|
+
|
38
|
+
class << self
|
39
|
+
def attribute(name)
|
40
|
+
new(name, '.')
|
41
|
+
end
|
42
|
+
|
43
|
+
def index(name)
|
44
|
+
new(name, '[', ']')
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,243 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# rubocop:disable Metrics/ClassLength
|
4
|
+
|
5
|
+
require_relative 'mixin/errors'
|
6
|
+
require_relative 'mixin/reflection'
|
7
|
+
require_relative 'mixin/handler_support'
|
8
|
+
require_relative 'context'
|
9
|
+
require_relative 'type/parameter'
|
10
|
+
require_relative 'type/attribute'
|
11
|
+
require_relative 'handler/entity/normalizer'
|
12
|
+
require_relative 'handler/entity/denormalizer'
|
13
|
+
require_relative 'handler/entity/enumerator'
|
14
|
+
require_relative 'handler/entity/injector'
|
15
|
+
require_relative 'handler/entity/factory'
|
16
|
+
require_relative 'handler/entity/validator'
|
17
|
+
|
18
|
+
module AMA
|
19
|
+
module Entity
|
20
|
+
class Mapper
|
21
|
+
# Type wrapper
|
22
|
+
class Type
|
23
|
+
include Mixin::Errors
|
24
|
+
include Mixin::Reflection
|
25
|
+
include Mixin::HandlerSupport
|
26
|
+
|
27
|
+
# @!attribute type
|
28
|
+
# @return [Class]
|
29
|
+
attr_accessor :type
|
30
|
+
# @!attribute parameters
|
31
|
+
# @return [Hash{Symbol, AMA::Entity::Mapper::Type::Parameter}]
|
32
|
+
attr_accessor :parameters
|
33
|
+
# @!attribute attributes
|
34
|
+
# @return [Hash{Symbol, AMA::Entity::Mapper::Type::Attribute}]
|
35
|
+
attr_accessor :attributes
|
36
|
+
# @!attribute virtual
|
37
|
+
# @return [TrueClass, FalseClass]
|
38
|
+
attr_accessor :virtual
|
39
|
+
|
40
|
+
handler_namespace Handler::Entity
|
41
|
+
|
42
|
+
# @!attribute factory
|
43
|
+
# @return [AMA::Entity::Mapper::Handler::Entity::Factory]
|
44
|
+
handler :factory, :create
|
45
|
+
# @!attribute normalizer
|
46
|
+
# @return [AMA::Entity::Mapper::Handler::Entity::Normalizer]
|
47
|
+
handler :normalizer, :normalize
|
48
|
+
# @!attribute denormalizer
|
49
|
+
# @return [AMA::Entity::Mapper::Handler::Entity::Denormalizer]
|
50
|
+
handler :denormalizer, :denormalize
|
51
|
+
# @!attribute enumerator
|
52
|
+
# @return [AMA::Entity::Mapper::Handler::Entity::Enumerator]
|
53
|
+
handler :enumerator, :enumerate
|
54
|
+
# @!attribute injector
|
55
|
+
# @return [AMA::Entity::Mapper::Handler::Entity::Injector]
|
56
|
+
handler :injector, :inject
|
57
|
+
# @!attribute injector
|
58
|
+
# @return [AMA::Entity::Mapper::Handler::Entity::Validator]
|
59
|
+
handler :validator, :validate
|
60
|
+
|
61
|
+
# @param [Class, Module] klass
|
62
|
+
def initialize(klass, virtual: false)
|
63
|
+
@type = validate_type!(klass)
|
64
|
+
@parameters = {}
|
65
|
+
@attributes = {}
|
66
|
+
@virtual = virtual
|
67
|
+
end
|
68
|
+
|
69
|
+
# Tells if provided object is an instance of this type.
|
70
|
+
#
|
71
|
+
# This doesn't mean all of it's attributes do match requested types.
|
72
|
+
#
|
73
|
+
# @param [Object] object
|
74
|
+
# @return [TrueClass, FalseClass]
|
75
|
+
def instance?(object)
|
76
|
+
object.is_a?(@type)
|
77
|
+
end
|
78
|
+
|
79
|
+
def instance!(object, context)
|
80
|
+
return if instance?(object)
|
81
|
+
message = "Provided object #{object} is not an instance of #{self}"
|
82
|
+
validation_error(message, context: context)
|
83
|
+
end
|
84
|
+
|
85
|
+
# @return [TrueClass, FalseClass]
|
86
|
+
def resolved?
|
87
|
+
attributes.values.all?(&:resolved?)
|
88
|
+
end
|
89
|
+
|
90
|
+
# Validates that type is fully resolved, otherwise raises an error
|
91
|
+
# @param [AMA::Entity::Mapper::Context] context
|
92
|
+
def resolved!(context = Context.new)
|
93
|
+
attributes.values.each { |attribute| attribute.resolved!(context) }
|
94
|
+
end
|
95
|
+
|
96
|
+
# Shortcut for attribute creation.
|
97
|
+
#
|
98
|
+
# @param [String, Symbol] name
|
99
|
+
# @param [Array<AMA::Entity::Mapper::Type>] types
|
100
|
+
# @param [Hash] options
|
101
|
+
def attribute!(name, *types, **options)
|
102
|
+
name = name.to_sym
|
103
|
+
types = types.map do |type|
|
104
|
+
next type if type.is_a?(Parameter)
|
105
|
+
next parameter!(type) if type.is_a?(Symbol)
|
106
|
+
next self.class.new(type) unless type.is_a?(Type)
|
107
|
+
type
|
108
|
+
end
|
109
|
+
attributes[name] = Attribute.new(self, name, *types, **options)
|
110
|
+
end
|
111
|
+
|
112
|
+
# Creates new type parameter
|
113
|
+
#
|
114
|
+
# @param [Symbol] id
|
115
|
+
# @return [Parameter]
|
116
|
+
def parameter!(id)
|
117
|
+
id = id.to_sym
|
118
|
+
return @parameters[id] if @parameters.key?(id)
|
119
|
+
@parameters[id] = Parameter.new(self, id)
|
120
|
+
end
|
121
|
+
|
122
|
+
# Resolves single parameter type. Substitution may be either another
|
123
|
+
# parameter or array of types.
|
124
|
+
#
|
125
|
+
# @param [Parameter] parameter
|
126
|
+
# @param [Parameter, Array<Type>] substitution
|
127
|
+
def resolve_parameter(parameter, substitution)
|
128
|
+
parameter = validate_parameter!(parameter)
|
129
|
+
substitution = validate_substitution!(substitution)
|
130
|
+
clone.tap do |clone|
|
131
|
+
intermediate = attributes.map do |key, value|
|
132
|
+
[key, value.resolve_parameter(parameter, substitution)]
|
133
|
+
end
|
134
|
+
clone.attributes = Hash[intermediate]
|
135
|
+
intermediate = clone.parameters.map do |key, value|
|
136
|
+
[key, value == parameter ? substitution : value]
|
137
|
+
end
|
138
|
+
clone.parameters = Hash[intermediate]
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
# rubocop:disable Metrics/LineLength
|
143
|
+
|
144
|
+
# @param [Hash<AMA::Entity::Mapper::Type, AMA::Entity::Mapper::Type>] parameters
|
145
|
+
# @return [AMA::Entity::Mapper::Type]
|
146
|
+
def resolve(parameters)
|
147
|
+
parameters.reduce(self) do |carrier, tuple|
|
148
|
+
carrier.resolve_parameter(*tuple)
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
# rubocop:enable Metrics/LineLength
|
153
|
+
|
154
|
+
def violations(object, context)
|
155
|
+
validator.validate(object, self, context)
|
156
|
+
end
|
157
|
+
|
158
|
+
def valid?(object, context)
|
159
|
+
violations(object, context).empty?
|
160
|
+
end
|
161
|
+
|
162
|
+
def valid!(object, context)
|
163
|
+
violations = self.violations(object, context)
|
164
|
+
return if violations.empty?
|
165
|
+
message = "#{object} has failed type #{to_def} validation: " \
|
166
|
+
"#{violations.join(', ')}"
|
167
|
+
validation_error(message, context: context)
|
168
|
+
end
|
169
|
+
|
170
|
+
def hash
|
171
|
+
@type.hash ^ @attributes.hash
|
172
|
+
end
|
173
|
+
|
174
|
+
def eql?(other)
|
175
|
+
return false unless other.is_a?(self.class)
|
176
|
+
@type == other.type && @attributes == other.attributes
|
177
|
+
end
|
178
|
+
|
179
|
+
def ==(other)
|
180
|
+
eql?(other)
|
181
|
+
end
|
182
|
+
|
183
|
+
def to_s
|
184
|
+
message = "Type #{@type}"
|
185
|
+
unless @parameters.empty?
|
186
|
+
message += " (parameters: #{@parameters.keys})"
|
187
|
+
end
|
188
|
+
message
|
189
|
+
end
|
190
|
+
|
191
|
+
def to_def
|
192
|
+
return @type.to_s if parameters.empty?
|
193
|
+
params = parameters.map do |key, value|
|
194
|
+
value = [value] unless value.is_a?(Enumerable)
|
195
|
+
value = value.map(&:to_def)
|
196
|
+
value = value.size > 1 ? "[#{value.join(', ')}]" : value.first
|
197
|
+
"#{key}:#{value}"
|
198
|
+
end
|
199
|
+
"#{@type}<#{params.join(', ')}>"
|
200
|
+
end
|
201
|
+
|
202
|
+
private
|
203
|
+
|
204
|
+
def validate_type!(type)
|
205
|
+
return type if type.is_a?(Class) || type.is_a?(Module)
|
206
|
+
message = 'Expected Type to be instantiated with ' \
|
207
|
+
"Class/Module instance, got #{type}"
|
208
|
+
compliance_error(message)
|
209
|
+
end
|
210
|
+
|
211
|
+
def validate_parameter!(parameter)
|
212
|
+
return parameter if parameter.is_a?(Parameter)
|
213
|
+
message = "Non-parameter type #{parameter} " \
|
214
|
+
'supplied for resolution'
|
215
|
+
compliance_error(message)
|
216
|
+
end
|
217
|
+
|
218
|
+
def validate_substitution!(substitution)
|
219
|
+
return substitution if substitution.is_a?(Parameter)
|
220
|
+
substitution = [substitution] if substitution.is_a?(self.class)
|
221
|
+
if substitution.is_a?(Enumerable)
|
222
|
+
return validate_substitutions!(substitution)
|
223
|
+
end
|
224
|
+
message = 'Provided substitution is neither another Parameter ' \
|
225
|
+
'or Array of Types: ' \
|
226
|
+
"#{substitution} (#{substitution.class})"
|
227
|
+
compliance_error(message)
|
228
|
+
end
|
229
|
+
|
230
|
+
def validate_substitutions!(substitutions)
|
231
|
+
if substitutions.empty?
|
232
|
+
compliance_error('Empty list of substitutions passed')
|
233
|
+
end
|
234
|
+
invalid = substitutions.reject do |substitution|
|
235
|
+
substitution.is_a?(Type)
|
236
|
+
end
|
237
|
+
return substitutions if invalid.empty?
|
238
|
+
compliance_error("Invalid substitutions supplied: #{invalid}")
|
239
|
+
end
|
240
|
+
end
|
241
|
+
end
|
242
|
+
end
|
243
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../type'
|
4
|
+
require_relative 'any'
|
5
|
+
|
6
|
+
module AMA
|
7
|
+
module Entity
|
8
|
+
class Mapper
|
9
|
+
class Type
|
10
|
+
# Some naive automatic attribute discovery
|
11
|
+
class Analyzer
|
12
|
+
# @param [Class, Module] klass
|
13
|
+
# @return [AMA::Entity::Mapper:Type]
|
14
|
+
def self.analyze(klass)
|
15
|
+
type = Type.new(klass)
|
16
|
+
writers = klass.instance_methods.grep(/\w+=$/)
|
17
|
+
writers.map do |writer|
|
18
|
+
attribute = writer[0..-2]
|
19
|
+
type.attribute!(attribute, Type::Any::INSTANCE, nullable: true)
|
20
|
+
end
|
21
|
+
type
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,66 @@
|
|
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
|
+
# Used as a wildcard to pass anything through
|
11
|
+
class Any < Type
|
12
|
+
include Mixin::Errors
|
13
|
+
|
14
|
+
def initialize
|
15
|
+
super(self.class)
|
16
|
+
denormalizer_block { |entity, *| entity }
|
17
|
+
normalizer_block { |entity, *| entity }
|
18
|
+
validator_block { |*| [] }
|
19
|
+
end
|
20
|
+
|
21
|
+
INSTANCE = new
|
22
|
+
|
23
|
+
def parameters
|
24
|
+
{}
|
25
|
+
end
|
26
|
+
|
27
|
+
def attributes
|
28
|
+
{}
|
29
|
+
end
|
30
|
+
|
31
|
+
def parameter!(*)
|
32
|
+
compliance_error('Tried to declare parameter on Any type')
|
33
|
+
end
|
34
|
+
|
35
|
+
def resolve_parameter(*)
|
36
|
+
self
|
37
|
+
end
|
38
|
+
|
39
|
+
def instance?(object, *)
|
40
|
+
!object.nil?
|
41
|
+
end
|
42
|
+
|
43
|
+
def hash
|
44
|
+
self.class.hash
|
45
|
+
end
|
46
|
+
|
47
|
+
def eql?(other)
|
48
|
+
other.is_a?(Type)
|
49
|
+
end
|
50
|
+
|
51
|
+
def ==(other)
|
52
|
+
eql?(other)
|
53
|
+
end
|
54
|
+
|
55
|
+
def to_s
|
56
|
+
'Any Type'
|
57
|
+
end
|
58
|
+
|
59
|
+
def to_def
|
60
|
+
'*'
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,197 @@
|
|
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
|
+
end
|
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
|
+
@owner = validate_owner!(owner)
|
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
|
+
def hash
|
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
|
+
def ==(other)
|
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
|
+
message += ':virtual' if virtual
|
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
|