dynamicschema 1.0.1 → 2.1.0
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 +4 -4
- data/README.md +456 -173
- data/dynamicschema.gemspec +7 -2
- data/lib/dynamic_schema/buildable.rb +8 -8
- data/lib/dynamic_schema/builder.rb +50 -19
- data/lib/dynamic_schema/{resolver.rb → compiler.rb} +42 -43
- data/lib/dynamic_schema/{builder_methods/conversion.rb → converter.rb} +41 -18
- data/lib/dynamic_schema/receiver/base.rb +60 -0
- data/lib/dynamic_schema/receiver/object.rb +301 -0
- data/lib/dynamic_schema/receiver/value.rb +27 -0
- data/lib/dynamic_schema/struct.rb +203 -0
- data/lib/dynamic_schema/validator.rb +139 -0
- data/lib/dynamic_schema.rb +7 -2
- metadata +18 -11
- data/lib/dynamic_schema/builder_methods/validation.rb +0 -109
- data/lib/dynamic_schema/receiver.rb +0 -227
metadata
CHANGED
|
@@ -1,28 +1,28 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: dynamicschema
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.0
|
|
4
|
+
version: 2.1.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Kristoph Cichocki-Romanov
|
|
8
8
|
bindir: bin
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
|
-
name:
|
|
13
|
+
name: minitest
|
|
14
14
|
requirement: !ruby/object:Gem::Requirement
|
|
15
15
|
requirements:
|
|
16
16
|
- - "~>"
|
|
17
17
|
- !ruby/object:Gem::Version
|
|
18
|
-
version: '
|
|
18
|
+
version: '6.0'
|
|
19
19
|
type: :development
|
|
20
20
|
prerelease: false
|
|
21
21
|
version_requirements: !ruby/object:Gem::Requirement
|
|
22
22
|
requirements:
|
|
23
23
|
- - "~>"
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
|
-
version: '
|
|
25
|
+
version: '6.0'
|
|
26
26
|
- !ruby/object:Gem::Dependency
|
|
27
27
|
name: debug
|
|
28
28
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -43,7 +43,11 @@ description: "The DynamicSchema gem provides a elegant and expressive way to def
|
|
|
43
43
|
configurations or interfacing with external APIs, where data structures need to
|
|
44
44
|
adhere to specific formats and validations. By allowing default values, type constraints,
|
|
45
45
|
nested schemas, and transformations, DynamicSchema ensures that your data structures
|
|
46
|
-
are both robust and flexible.
|
|
46
|
+
are both robust and flexible. \n\nNew in 2.0, DynamicSchema adds DynamicSchema::Struct
|
|
47
|
+
which faciliates effortless definition and construction of complex object hierarchies,
|
|
48
|
+
with optional type coersion and validation. Where DynamicSchema simplified configuration
|
|
49
|
+
and API payload construction, DynamicSchema::Struct simplifies construction of
|
|
50
|
+
complex API reponses."
|
|
47
51
|
email:
|
|
48
52
|
- rubygems.org@kristoph.net
|
|
49
53
|
executables: []
|
|
@@ -56,12 +60,15 @@ files:
|
|
|
56
60
|
- lib/dynamic_schema.rb
|
|
57
61
|
- lib/dynamic_schema/buildable.rb
|
|
58
62
|
- lib/dynamic_schema/builder.rb
|
|
59
|
-
- lib/dynamic_schema/
|
|
60
|
-
- lib/dynamic_schema/
|
|
63
|
+
- lib/dynamic_schema/compiler.rb
|
|
64
|
+
- lib/dynamic_schema/converter.rb
|
|
61
65
|
- lib/dynamic_schema/definable.rb
|
|
62
66
|
- lib/dynamic_schema/errors.rb
|
|
63
|
-
- lib/dynamic_schema/receiver.rb
|
|
64
|
-
- lib/dynamic_schema/
|
|
67
|
+
- lib/dynamic_schema/receiver/base.rb
|
|
68
|
+
- lib/dynamic_schema/receiver/object.rb
|
|
69
|
+
- lib/dynamic_schema/receiver/value.rb
|
|
70
|
+
- lib/dynamic_schema/struct.rb
|
|
71
|
+
- lib/dynamic_schema/validator.rb
|
|
65
72
|
homepage: https://github.com/EndlessInternational/dynamic_schema
|
|
66
73
|
licenses:
|
|
67
74
|
- MIT
|
|
@@ -82,7 +89,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
82
89
|
- !ruby/object:Gem::Version
|
|
83
90
|
version: '0'
|
|
84
91
|
requirements: []
|
|
85
|
-
rubygems_version: 3.6.
|
|
92
|
+
rubygems_version: 3.6.7
|
|
86
93
|
specification_version: 4
|
|
87
94
|
summary: DynamicSchema is a lightweight and simple yet powerful gem that enables flexible
|
|
88
95
|
semantic schema definitions for constructing and validating complex configurations
|
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
module DynamicSchema
|
|
2
|
-
module BuilderMethods
|
|
3
|
-
module Validation
|
|
4
|
-
|
|
5
|
-
def validate!( values, schema: self.schema )
|
|
6
|
-
traverse_and_validate_values( values, schema: schema ) { | error |
|
|
7
|
-
raise error
|
|
8
|
-
}
|
|
9
|
-
end
|
|
10
|
-
|
|
11
|
-
def validate( values, schema: self.schema )
|
|
12
|
-
errors = []
|
|
13
|
-
traverse_and_validate_values( values, schema: schema ) { | error |
|
|
14
|
-
errors << error
|
|
15
|
-
}
|
|
16
|
-
errors
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
def valid?( values, schema: self.schema )
|
|
20
|
-
traverse_and_validate_values( values, schema: schema ) {
|
|
21
|
-
return false
|
|
22
|
-
}
|
|
23
|
-
return true
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
protected
|
|
27
|
-
|
|
28
|
-
def value_matches_types?( value, types )
|
|
29
|
-
type_match = false
|
|
30
|
-
Array( types ).each do | type |
|
|
31
|
-
type_match = value.is_a?( type )
|
|
32
|
-
break if type_match
|
|
33
|
-
end
|
|
34
|
-
type_match
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
def traverse_and_validate_values( values, schema:, path: nil, options: nil, &block )
|
|
38
|
-
path.chomp( '/' ) if path
|
|
39
|
-
unless values.is_a?( Hash )
|
|
40
|
-
raise ArgumentError, "The values must always be a Hash."
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
schema.each do | key, criteria |
|
|
44
|
-
|
|
45
|
-
name = criteria[ :as ] || key
|
|
46
|
-
value = values[ name ]
|
|
47
|
-
|
|
48
|
-
if criteria[ :required ] &&
|
|
49
|
-
( !value || ( value.respond_to?( :empty ) && value.empty? ) )
|
|
50
|
-
block.call( RequiredOptionError.new( path: path, key: key ) )
|
|
51
|
-
elsif criteria[ :in ]
|
|
52
|
-
Array( value ).each do | v |
|
|
53
|
-
unless criteria[ :in ].include?( v ) || v.nil?
|
|
54
|
-
block.call(
|
|
55
|
-
InOptionError.new( path: path, key: key, option: criteria[ :in ], value: v )
|
|
56
|
-
)
|
|
57
|
-
end
|
|
58
|
-
end
|
|
59
|
-
elsif !criteria[ :default_assigned ] && !value.nil?
|
|
60
|
-
unless criteria[ :array ]
|
|
61
|
-
if criteria[ :type ] == Object
|
|
62
|
-
traverse_and_validate_values(
|
|
63
|
-
values[ name ],
|
|
64
|
-
schema: criteria[ :schema ] ||= criteria[ :resolver ]._schema,
|
|
65
|
-
path: "#{ ( path || '' ) + ( path ? '/' : '' ) + key.to_s }",
|
|
66
|
-
&block
|
|
67
|
-
)
|
|
68
|
-
else
|
|
69
|
-
if criteria[ :type ] && value && !criteria[ :default_assigned ]
|
|
70
|
-
unless value_matches_types?( value, criteria[ :type ] )
|
|
71
|
-
block.call( IncompatibleTypeError.new(
|
|
72
|
-
path: path, key: key, type: criteria[ :type ], value: value
|
|
73
|
-
) )
|
|
74
|
-
end
|
|
75
|
-
end
|
|
76
|
-
end
|
|
77
|
-
else
|
|
78
|
-
if criteria[ :type ] == Object
|
|
79
|
-
groups = Array( value )
|
|
80
|
-
groups.each do | group |
|
|
81
|
-
traverse_and_validate_values(
|
|
82
|
-
group,
|
|
83
|
-
schema: criteria[ :schema ] ||= criteria[ :resolver ]._schema,
|
|
84
|
-
path: "#{ ( path || '' ) + ( path ? '/' : '' ) + key.to_s }",
|
|
85
|
-
&block
|
|
86
|
-
)
|
|
87
|
-
end
|
|
88
|
-
else
|
|
89
|
-
if criteria[ :type ] && !criteria[ :default_assigned ]
|
|
90
|
-
value_array = Array( value )
|
|
91
|
-
value_array.each do | v |
|
|
92
|
-
unless value_matches_types?( v, criteria[ :type ] )
|
|
93
|
-
block.call( IncompatibleTypeError.new(
|
|
94
|
-
path: path, key: key, type: criteria[ :type ], value: v
|
|
95
|
-
) )
|
|
96
|
-
end
|
|
97
|
-
end
|
|
98
|
-
end
|
|
99
|
-
end
|
|
100
|
-
end
|
|
101
|
-
end
|
|
102
|
-
|
|
103
|
-
end
|
|
104
|
-
nil
|
|
105
|
-
end
|
|
106
|
-
|
|
107
|
-
end
|
|
108
|
-
end
|
|
109
|
-
end
|
|
@@ -1,227 +0,0 @@
|
|
|
1
|
-
module DynamicSchema
|
|
2
|
-
class Receiver < BasicObject
|
|
3
|
-
|
|
4
|
-
if defined?( ::PP )
|
|
5
|
-
include ::PP::ObjectMixin
|
|
6
|
-
def pretty_print( pp )
|
|
7
|
-
pp.pp( { values: @values, schema: @schema } )
|
|
8
|
-
end
|
|
9
|
-
end
|
|
10
|
-
|
|
11
|
-
def initialize( values = nil, schema:, converters: )
|
|
12
|
-
raise ArgumentError, 'The Receiver values must be a nil or a Hash.'\
|
|
13
|
-
unless values.nil? || ( values.respond_to?( :[] ) && values.respond_to?( :key? ) )
|
|
14
|
-
|
|
15
|
-
@values = {}
|
|
16
|
-
@schema = schema
|
|
17
|
-
@defaults_assigned = {}
|
|
18
|
-
|
|
19
|
-
@converters = converters&.dup
|
|
20
|
-
|
|
21
|
-
@schema.each do | key, criteria |
|
|
22
|
-
name = criteria[ :as ] || key
|
|
23
|
-
if criteria.key?( :default )
|
|
24
|
-
self.__send__( key, criteria[ :default ] )
|
|
25
|
-
@defaults_assigned[ key ] = true
|
|
26
|
-
end
|
|
27
|
-
self.__send__( key, values[ key ] ) if values && values.key?( key )
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
def evaluate( &block )
|
|
33
|
-
self.instance_eval( &block )
|
|
34
|
-
self
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
def nil?
|
|
38
|
-
false
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
def empty?
|
|
42
|
-
@values.empty?
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
def to_h
|
|
46
|
-
recursive_to_h = ->( object ) do
|
|
47
|
-
case object
|
|
48
|
-
when ::NilClass
|
|
49
|
-
nil
|
|
50
|
-
when ::DynamicSchema::Receiver
|
|
51
|
-
recursive_to_h.call( object.to_h )
|
|
52
|
-
when ::Hash
|
|
53
|
-
object.transform_values { | value | recursive_to_h.call( value ) }
|
|
54
|
-
when ::Array
|
|
55
|
-
object.map { | element | recursive_to_h.call( element ) }
|
|
56
|
-
else
|
|
57
|
-
object.respond_to?( :to_h ) ? object.to_h : object
|
|
58
|
-
end
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
recursive_to_h.call( @values )
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
def to_s
|
|
65
|
-
inspect
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
def inspect
|
|
69
|
-
{ values: @values, schema: @schema }.inspect
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
def class
|
|
73
|
-
::DynamicSchema::Receiver
|
|
74
|
-
end
|
|
75
|
-
|
|
76
|
-
def is_a?( klass )
|
|
77
|
-
klass == ::DynamicSchema::Receiver || klass == ::BasicObject
|
|
78
|
-
end
|
|
79
|
-
|
|
80
|
-
alias :kind_of? :is_a?
|
|
81
|
-
|
|
82
|
-
def method_missing( method, *args, &block )
|
|
83
|
-
if @schema.key?( method )
|
|
84
|
-
criteria = @schema[ method ]
|
|
85
|
-
name = criteria[ :as ] || method
|
|
86
|
-
value = @values[ name ]
|
|
87
|
-
|
|
88
|
-
unless criteria[ :array ]
|
|
89
|
-
if criteria[ :type ] == ::Object
|
|
90
|
-
value = __object( method, args, value: value, criteria: criteria, &block )
|
|
91
|
-
else
|
|
92
|
-
value = __value( method, args, value: value, criteria: criteria )
|
|
93
|
-
end
|
|
94
|
-
else
|
|
95
|
-
value = @defaults_assigned[ method ] ? ::Array.new : value || ::Array.new
|
|
96
|
-
if criteria[ :type ] == ::Object
|
|
97
|
-
value = __object_array( method, args, value: value, criteria: criteria, &block )
|
|
98
|
-
else
|
|
99
|
-
value = __values_array( method, args, value: value, criteria: criteria )
|
|
100
|
-
end
|
|
101
|
-
end
|
|
102
|
-
|
|
103
|
-
@defaults_assigned[ method ] = false
|
|
104
|
-
@values[ name ] = value
|
|
105
|
-
else
|
|
106
|
-
::Kernel.raise ::NoMethodError,
|
|
107
|
-
"There is no schema value or object '#{method}' defined in this scope which includes: " \
|
|
108
|
-
"#{@schema.keys.join( ', ' )}."
|
|
109
|
-
end
|
|
110
|
-
end
|
|
111
|
-
|
|
112
|
-
def respond_to?( method, include_private = false )
|
|
113
|
-
@schema.key?( method ) || self.class.instance_methods.include?( method )
|
|
114
|
-
end
|
|
115
|
-
|
|
116
|
-
def respond_to_missing?( method, include_private = false )
|
|
117
|
-
@schema.key?( method ) || self.class.instance_methods.include?( method )
|
|
118
|
-
end
|
|
119
|
-
|
|
120
|
-
protected
|
|
121
|
-
|
|
122
|
-
def __process_arguments( name, arguments, required_arguments: )
|
|
123
|
-
count = arguments.count
|
|
124
|
-
required_arguments = [ required_arguments ].flatten if required_arguments
|
|
125
|
-
required_count = required_arguments&.length || 0
|
|
126
|
-
::Kernel.raise ::ArgumentError,
|
|
127
|
-
"The attribute '#{name}' requires #{required_count} arguments " \
|
|
128
|
-
"(#{required_arguments.join(', ')}) but #{count} was given." \
|
|
129
|
-
if count < required_count
|
|
130
|
-
::Kernel.raise ::ArgumentError,
|
|
131
|
-
"The attribute '#{name}' should have at most #{required_count + 1} arguments but " \
|
|
132
|
-
"#{count} was given." \
|
|
133
|
-
if count > required_count + 1
|
|
134
|
-
|
|
135
|
-
result = {}
|
|
136
|
-
|
|
137
|
-
required_arguments&.each_with_index do | name, index |
|
|
138
|
-
result[ name.to_sym ] = arguments[ index ]
|
|
139
|
-
end
|
|
140
|
-
arguments.slice!( 0, required_arguments.length ) if required_arguments
|
|
141
|
-
|
|
142
|
-
last = arguments.last
|
|
143
|
-
case last
|
|
144
|
-
when ::Hash
|
|
145
|
-
result.merge( last )
|
|
146
|
-
when ::Array
|
|
147
|
-
last.map { | v | result.merge( v ) }
|
|
148
|
-
else
|
|
149
|
-
result
|
|
150
|
-
end
|
|
151
|
-
end
|
|
152
|
-
|
|
153
|
-
def __coerce_value( types, value )
|
|
154
|
-
return value unless types && !value.nil?
|
|
155
|
-
|
|
156
|
-
types = ::Kernel.method( :Array ).call( types )
|
|
157
|
-
result = nil
|
|
158
|
-
|
|
159
|
-
if value.respond_to?( :is_a? )
|
|
160
|
-
types.each do | type |
|
|
161
|
-
result = value.is_a?( type ) ? value : nil
|
|
162
|
-
break unless result.nil?
|
|
163
|
-
end
|
|
164
|
-
end
|
|
165
|
-
|
|
166
|
-
if result.nil?
|
|
167
|
-
types.each do | type |
|
|
168
|
-
result = @converters[ type ].call( value ) rescue nil
|
|
169
|
-
break unless result.nil?
|
|
170
|
-
end
|
|
171
|
-
end
|
|
172
|
-
|
|
173
|
-
result
|
|
174
|
-
end
|
|
175
|
-
|
|
176
|
-
def __value( method, arguments, value:, criteria: )
|
|
177
|
-
value = arguments.first
|
|
178
|
-
new_value = criteria[ :type ] ? __coerce_value( criteria[ :type ], value ) : value
|
|
179
|
-
new_value.nil? ? value : new_value
|
|
180
|
-
end
|
|
181
|
-
|
|
182
|
-
def __values_array( method, arguments, value:, criteria: )
|
|
183
|
-
values = [ arguments.first ].flatten
|
|
184
|
-
if type = criteria[ :type ]
|
|
185
|
-
values = values.map do | v |
|
|
186
|
-
new_value = __coerce_value( type, v )
|
|
187
|
-
new_value.nil? ? v : new_value
|
|
188
|
-
end
|
|
189
|
-
end
|
|
190
|
-
value.concat( values )
|
|
191
|
-
end
|
|
192
|
-
|
|
193
|
-
def __object( method, arguments, value:, criteria:, &block )
|
|
194
|
-
attributes = __process_arguments(
|
|
195
|
-
method, arguments,
|
|
196
|
-
required_arguments: criteria[ :arguments ]
|
|
197
|
-
)
|
|
198
|
-
if value.nil? || attributes&.any?
|
|
199
|
-
value =
|
|
200
|
-
Receiver.new(
|
|
201
|
-
attributes,
|
|
202
|
-
converters: @converters,
|
|
203
|
-
schema: criteria[ :schema ] ||= criteria[ :resolver ]._schema
|
|
204
|
-
)
|
|
205
|
-
end
|
|
206
|
-
value.instance_eval( &block ) if block
|
|
207
|
-
value
|
|
208
|
-
end
|
|
209
|
-
|
|
210
|
-
def __object_array( method, arguments, value:, criteria:, &block )
|
|
211
|
-
attributes = __process_arguments(
|
|
212
|
-
method, arguments,
|
|
213
|
-
required_arguments: criteria[ :arguments ]
|
|
214
|
-
)
|
|
215
|
-
value.concat( [ attributes ].flatten.map { | a |
|
|
216
|
-
receiver = Receiver.new(
|
|
217
|
-
a,
|
|
218
|
-
converters: @converters,
|
|
219
|
-
schema: criteria[ :schema ] ||= criteria[ :resolver ]._schema
|
|
220
|
-
)
|
|
221
|
-
receiver.instance_eval( &block ) if block
|
|
222
|
-
receiver
|
|
223
|
-
} )
|
|
224
|
-
end
|
|
225
|
-
|
|
226
|
-
end
|
|
227
|
-
end
|