dynamicschema 2.1.0 → 2.2.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 +86 -0
- data/dynamicschema.gemspec +1 -1
- data/lib/dynamic_schema/receiver/object.rb +23 -15
- data/lib/dynamic_schema/struct.rb +32 -17
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 02ef93530db92a0a71dbd58cb6fef978b72d11d6d92704d4ca9714bc2a329beb
|
|
4
|
+
data.tar.gz: 0a846b974a9c41dfe5dd3caf1034f81a75974bbf9eb1b00b743f5f16bfb1fd06
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f6ef7406250f00130c77dcf5bd1c025d7185616d96a6d96193979c2c2ad57098bb3b80952225759b8d07baf537d2cd3c0746ea4b23d504e12937fcf19d7353bd
|
|
7
|
+
data.tar.gz: 943d6dd203630808c5838f484b4c77fa6b0511b2d23dd24e076ddb8aa22c9fbea74d05fb291b7c915b3c176db99f7eff9afb19a27e5b65c90d823744328b7237
|
data/README.md
CHANGED
|
@@ -55,6 +55,7 @@ You can find a full OpenAI request example in the `/examples` folder of this rep
|
|
|
55
55
|
- [as Option](#as-option)
|
|
56
56
|
- [in Option (Values Only)](#in-option)
|
|
57
57
|
- [arguments Option](#arguments-option)
|
|
58
|
+
- [normalize Option](#normalize-option)
|
|
58
59
|
- [Class Schema](#class-schemas)
|
|
59
60
|
- [Definable](#definable)
|
|
60
61
|
- [Buildable](#buildable)
|
|
@@ -718,6 +719,91 @@ result = schema.build! do
|
|
|
718
719
|
end
|
|
719
720
|
```
|
|
720
721
|
|
|
722
|
+
### :normalize Option
|
|
723
|
+
|
|
724
|
+
The `:normalize` option allows you to transform values immediately upon assignment. It accepts a lambda or Proc that receives the value and returns the transformed result. When used with arrays, the normalizer is called for each individual item, not the array itself.
|
|
725
|
+
|
|
726
|
+
#### example:
|
|
727
|
+
|
|
728
|
+
```ruby
|
|
729
|
+
schema = DynamicSchema.define do
|
|
730
|
+
name String, normalize: ->( v ) { v.strip.downcase }
|
|
731
|
+
tags String, array: true, normalize: ->( v ) { v.upcase }
|
|
732
|
+
score Integer, normalize: ->( v ) { v.clamp( 0, 100 ) }
|
|
733
|
+
end
|
|
734
|
+
|
|
735
|
+
result = schema.build! do
|
|
736
|
+
name ' Alice '
|
|
737
|
+
tags [ 'important', 'urgent' ]
|
|
738
|
+
score 150
|
|
739
|
+
end
|
|
740
|
+
|
|
741
|
+
puts result[:name] # => "alice"
|
|
742
|
+
puts result[:tags] # => ["IMPORTANT", "URGENT"]
|
|
743
|
+
puts result[:score] # => 100
|
|
744
|
+
```
|
|
745
|
+
|
|
746
|
+
The normalizer is applied after type coercion, so you receive the properly typed value:
|
|
747
|
+
|
|
748
|
+
```ruby
|
|
749
|
+
schema = DynamicSchema.define do
|
|
750
|
+
count Integer, normalize: ->( v ) { v * 2 }
|
|
751
|
+
end
|
|
752
|
+
|
|
753
|
+
result = schema.build! do
|
|
754
|
+
count '5' # coerced to Integer first, then normalized
|
|
755
|
+
end
|
|
756
|
+
|
|
757
|
+
puts result[:count] # => 10
|
|
758
|
+
```
|
|
759
|
+
|
|
760
|
+
You can also use normalize with objects to transform the entire nested structure:
|
|
761
|
+
|
|
762
|
+
```ruby
|
|
763
|
+
CustomUser = Struct.new( :name, :email )
|
|
764
|
+
|
|
765
|
+
schema = DynamicSchema.define do
|
|
766
|
+
user normalize: ->( v ) { CustomUser.new( v.to_h[:name], v.to_h[:email] ) } do
|
|
767
|
+
name String
|
|
768
|
+
email String
|
|
769
|
+
end
|
|
770
|
+
end
|
|
771
|
+
|
|
772
|
+
result = schema.build! do
|
|
773
|
+
user do
|
|
774
|
+
name 'Alice'
|
|
775
|
+
email 'alice@example.com'
|
|
776
|
+
end
|
|
777
|
+
end
|
|
778
|
+
|
|
779
|
+
result[:user].class # => CustomUser
|
|
780
|
+
result[:user].name # => "Alice"
|
|
781
|
+
```
|
|
782
|
+
|
|
783
|
+
The normalize option works with Struct as well:
|
|
784
|
+
|
|
785
|
+
```ruby
|
|
786
|
+
Person = DynamicSchema::Struct.define do
|
|
787
|
+
name String, normalize: ->( v ) { v.strip.capitalize }
|
|
788
|
+
tags String, array: true, normalize: ->( v ) { v.downcase }
|
|
789
|
+
end
|
|
790
|
+
|
|
791
|
+
person = Person.build( name: ' alice ', tags: [ 'ADMIN', 'USER' ] )
|
|
792
|
+
person.name # => "Alice"
|
|
793
|
+
person.tags # => ["admin", "user"]
|
|
794
|
+
```
|
|
795
|
+
|
|
796
|
+
Note that any exceptions raised within a normalize lambda are not captured and will propagate to the caller:
|
|
797
|
+
|
|
798
|
+
```ruby
|
|
799
|
+
schema = DynamicSchema.define do
|
|
800
|
+
value Integer, normalize: ->( v ) { raise "invalid value" if v < 0; v }
|
|
801
|
+
end
|
|
802
|
+
|
|
803
|
+
# This will raise RuntimeError: "invalid value"
|
|
804
|
+
schema.build! { value -1 }
|
|
805
|
+
```
|
|
806
|
+
|
|
721
807
|
## Class Schemas
|
|
722
808
|
|
|
723
809
|
DynamicSchema provides a number of modules you can include into your own classes to simplify their definition and construction.
|
data/dynamicschema.gemspec
CHANGED
|
@@ -167,43 +167,45 @@ module DynamicSchema
|
|
|
167
167
|
value = arguments.first
|
|
168
168
|
new_value = criteria[ :type ] ? __coerce_value( criteria[ :type ], value ) : value
|
|
169
169
|
new_value = new_value.nil? ? value : new_value
|
|
170
|
-
block ?
|
|
171
|
-
__value_block( method, value: new_value, criteria: criteria, &block ) :
|
|
170
|
+
new_value = block ?
|
|
171
|
+
__value_block( method, value: new_value, criteria: criteria, &block ) :
|
|
172
172
|
new_value
|
|
173
|
+
__normalize( new_value, criteria )
|
|
173
174
|
end
|
|
174
175
|
|
|
175
176
|
def __values_array( method, arguments, value:, criteria:, &block )
|
|
176
177
|
values = [ arguments.first ].flatten
|
|
177
178
|
if type = criteria[ :type ]
|
|
178
|
-
values = values.map do | v |
|
|
179
|
+
values = values.map do | v |
|
|
179
180
|
new_value = __coerce_value( type, v )
|
|
180
181
|
new_value.nil? ? v : new_value
|
|
181
|
-
end
|
|
182
|
+
end
|
|
182
183
|
end
|
|
183
|
-
if block
|
|
184
|
-
values = values.map do | value |
|
|
184
|
+
if block
|
|
185
|
+
values = values.map do | value |
|
|
185
186
|
__value_block( method, value: value, criteria: criteria, &block )
|
|
186
187
|
end
|
|
187
188
|
end
|
|
189
|
+
values = values.map { | v | __normalize( v, criteria ) }
|
|
188
190
|
value.concat( values )
|
|
189
191
|
end
|
|
190
192
|
|
|
191
193
|
def __object( method, arguments, value:, criteria:, &block )
|
|
192
|
-
attributes = __process_arguments(
|
|
193
|
-
method, arguments,
|
|
194
|
-
required_arguments: criteria[ :arguments ]
|
|
194
|
+
attributes = __process_arguments(
|
|
195
|
+
method, arguments,
|
|
196
|
+
required_arguments: criteria[ :arguments ]
|
|
195
197
|
)
|
|
196
|
-
if value.nil? || attributes&.any?
|
|
197
|
-
value =
|
|
198
|
-
Receiver::Object.new(
|
|
198
|
+
if value.nil? || attributes&.any?
|
|
199
|
+
value =
|
|
200
|
+
Receiver::Object.new(
|
|
199
201
|
attributes,
|
|
200
|
-
converter: @converter,
|
|
202
|
+
converter: @converter,
|
|
201
203
|
schema: criteria[ :schema ] ||= criteria[ :compiler ].compiled,
|
|
202
204
|
defaults: @defaults
|
|
203
205
|
)
|
|
204
206
|
end
|
|
205
207
|
value.instance_eval( &block ) if block
|
|
206
|
-
value
|
|
208
|
+
__normalize( value, criteria )
|
|
207
209
|
end
|
|
208
210
|
|
|
209
211
|
def __object_array( method, arguments, value:, criteria:, &block )
|
|
@@ -219,7 +221,7 @@ module DynamicSchema
|
|
|
219
221
|
defaults: @defaults
|
|
220
222
|
)
|
|
221
223
|
receiver.instance_eval( &block ) if block
|
|
222
|
-
receiver
|
|
224
|
+
__normalize( receiver, criteria )
|
|
223
225
|
} )
|
|
224
226
|
end
|
|
225
227
|
|
|
@@ -296,6 +298,12 @@ module DynamicSchema
|
|
|
296
298
|
value
|
|
297
299
|
end
|
|
298
300
|
|
|
301
|
+
def __normalize( value, criteria )
|
|
302
|
+
normalize = criteria[ :normalize ]
|
|
303
|
+
return value unless normalize && !value.nil?
|
|
304
|
+
normalize.call( value )
|
|
305
|
+
end
|
|
306
|
+
|
|
299
307
|
end
|
|
300
308
|
end
|
|
301
309
|
end
|
|
@@ -47,47 +47,56 @@ module DynamicSchema
|
|
|
47
47
|
has_multiple_types = type.is_a?( ::Array ) && criteria[ :compiler ]
|
|
48
48
|
|
|
49
49
|
if has_multiple_types
|
|
50
|
+
_criteria = criteria
|
|
50
51
|
define_method( property ) do
|
|
51
52
|
@converted_attributes.fetch( key ) do
|
|
52
53
|
value = @attributes[ key ]
|
|
53
54
|
value = default if value.nil?
|
|
54
|
-
schema =
|
|
55
|
-
klass =
|
|
55
|
+
schema = _criteria[ :schema ] ||= ( _criteria[ :compiler ]&.compiled )
|
|
56
|
+
klass = _criteria[ :class ] ||= ( schema ? ::DynamicSchema::Struct.new( schema ) : nil )
|
|
56
57
|
@converted_attributes[ key ] =
|
|
57
|
-
if
|
|
58
|
-
__convert_types_array( Array( value ), klass,
|
|
58
|
+
if _criteria[ :array ]
|
|
59
|
+
__convert_types_array( Array( value ), klass, _criteria )
|
|
59
60
|
else
|
|
60
|
-
__convert_types( value, klass,
|
|
61
|
+
__normalize( __convert_types( value, klass, _criteria ), _criteria )
|
|
61
62
|
end
|
|
62
63
|
end
|
|
63
64
|
end
|
|
64
65
|
elsif type == ::Object
|
|
66
|
+
_criteria = criteria
|
|
65
67
|
define_method( property ) do
|
|
66
68
|
@converted_attributes.fetch( key ) do
|
|
67
69
|
value = @attributes[ key ]
|
|
68
|
-
schema =
|
|
70
|
+
schema = _criteria[ :schema ] ||= ( _criteria[ :compiler ]&.compiled )
|
|
69
71
|
return value unless schema
|
|
70
|
-
klass =
|
|
72
|
+
klass = _criteria[ :class ] ||= ::DynamicSchema::Struct.new( schema )
|
|
71
73
|
@converted_attributes[ key ] =
|
|
72
|
-
if
|
|
73
|
-
Array( value || default ).map { | v | klass.build( v || {} ) }
|
|
74
|
+
if _criteria[ :array ]
|
|
75
|
+
Array( value || default ).map { | v | __normalize( klass.build( v || {} ), _criteria ) }
|
|
74
76
|
else
|
|
75
|
-
klass.build( value || default )
|
|
77
|
+
__normalize( klass.build( value || default ), _criteria )
|
|
76
78
|
end
|
|
77
79
|
end
|
|
78
80
|
end
|
|
79
81
|
elsif type
|
|
82
|
+
_criteria = criteria
|
|
80
83
|
define_method( property ) do
|
|
81
84
|
@converted_attributes.fetch( key ) do
|
|
82
85
|
value = @attributes[ key ]
|
|
83
|
-
@converted_attributes[ key ] =
|
|
84
|
-
Array( value || default ).map { | v |
|
|
85
|
-
|
|
86
|
+
@converted_attributes[ key ] = _criteria[ :array ] ?
|
|
87
|
+
Array( value || default ).map { | v | __normalize( __coerce( v, to: type ), _criteria ) } :
|
|
88
|
+
__normalize( __coerce( value || default, to: type ), _criteria )
|
|
86
89
|
end
|
|
87
90
|
end
|
|
88
91
|
else
|
|
92
|
+
_criteria = criteria
|
|
89
93
|
define_method( property ) do
|
|
90
|
-
@attributes[ key ] || default
|
|
94
|
+
value = @attributes[ key ] || default
|
|
95
|
+
if _criteria[ :array ]
|
|
96
|
+
Array( value ).map { | v | __normalize( v, _criteria ) }
|
|
97
|
+
else
|
|
98
|
+
__normalize( value, _criteria )
|
|
99
|
+
end
|
|
91
100
|
end
|
|
92
101
|
end
|
|
93
102
|
|
|
@@ -135,7 +144,7 @@ module DynamicSchema
|
|
|
135
144
|
end
|
|
136
145
|
end
|
|
137
146
|
|
|
138
|
-
def
|
|
147
|
+
def __coerce( value, to: )
|
|
139
148
|
self.class.converter.convert( value, to: to ) { | v | to.new( v ) rescue nil }
|
|
140
149
|
end
|
|
141
150
|
|
|
@@ -145,12 +154,12 @@ module DynamicSchema
|
|
|
145
154
|
klass ? klass.build( value ) : value
|
|
146
155
|
else
|
|
147
156
|
scalar_types = __non_object_types( criteria[ :type ] )
|
|
148
|
-
|
|
157
|
+
__coerce( value, to: scalar_types )
|
|
149
158
|
end
|
|
150
159
|
end
|
|
151
160
|
|
|
152
161
|
def __convert_types_array( values, klass, criteria )
|
|
153
|
-
values.map { | v | __convert_types( v, klass, criteria ) }
|
|
162
|
+
values.map { | v | __normalize( __convert_types( v, klass, criteria ), criteria ) }
|
|
154
163
|
end
|
|
155
164
|
|
|
156
165
|
def __non_object_types( types )
|
|
@@ -159,6 +168,12 @@ module DynamicSchema
|
|
|
159
168
|
result.length == 1 ? result.first : result
|
|
160
169
|
end
|
|
161
170
|
|
|
171
|
+
def __normalize( value, criteria )
|
|
172
|
+
normalize = criteria[ :normalize ]
|
|
173
|
+
return value unless normalize && !value.nil?
|
|
174
|
+
normalize.call( value )
|
|
175
|
+
end
|
|
176
|
+
|
|
162
177
|
end
|
|
163
178
|
__klass.instance_variable_set( :@__compiled_schema, __compiled_schema.dup )
|
|
164
179
|
__klass.instance_variable_set( :@__converter, __converter )
|