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.
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.1
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: 2025-07-28 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
- name: rspec
13
+ name: minitest
14
14
  requirement: !ruby/object:Gem::Requirement
15
15
  requirements:
16
16
  - - "~>"
17
17
  - !ruby/object:Gem::Version
18
- version: '3.13'
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: '3.13'
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/builder_methods/conversion.rb
60
- - lib/dynamic_schema/builder_methods/validation.rb
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/resolver.rb
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.2
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