dynamicschema 1.0.0 → 2.0.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 +195 -35
- data/dynamicschema.gemspec +6 -1
- data/lib/dynamic_schema/buildable.rb +8 -8
- data/lib/dynamic_schema/builder.rb +43 -18
- data/lib/dynamic_schema/{resolver.rb → compiler.rb} +29 -35
- 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.rb → receiver/object.rb} +77 -49
- data/lib/dynamic_schema/receiver/value.rb +27 -0
- data/lib/dynamic_schema/struct.rb +167 -0
- data/lib/dynamic_schema/validator.rb +105 -0
- data/lib/dynamic_schema.rb +7 -2
- metadata +18 -8
- data/lib/dynamic_schema/builder_methods/validation.rb +0 -109
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 96f20951dea52b231f5f4f9c8c5eae91647f2688be56877066a0aafd694c980a
|
4
|
+
data.tar.gz: 7a33def23c8a542d583d6f7b17fb831b564516ae3e3d5700f095eb51356c8b80
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3d746d4a0fc541475c2aea4bac6d9780d2d044ebe00987b18725f5c300ecaefa3348fd0dd74ca46f58c15c93d758ee5427c3727ecab696da0f6b44e21e4ff26d
|
7
|
+
data.tar.gz: ea6bee74de0c880dba3f6ea44a0f885d0885ef3e845c6d2d07d846dcf40724f625cdb4c599f29386ae67427cf2697c2d968bd7a1ef19c1c0f7b52677eefdafb6
|
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# DynamicSchema
|
2
2
|
|
3
|
-
The **DynamicSchema** gem provides
|
3
|
+
The **DynamicSchema** gem provides an elegant and expressive way to define domain-specific
|
4
4
|
language (DSL) schemas, making it effortless to build and validate complex Ruby `Hash`
|
5
5
|
constructs.
|
6
6
|
|
@@ -27,7 +27,7 @@ openai_request_schema = DynamicSchema.define do
|
|
27
27
|
end
|
28
28
|
```
|
29
29
|
|
30
|
-
And then
|
30
|
+
And then repeatedly use that schema to elegantly build a schema-conformant `Hash`:
|
31
31
|
```ruby
|
32
32
|
request = openai_request_schema.build {
|
33
33
|
message :system do
|
@@ -60,6 +60,7 @@ You can find a full OpenAI request example in the `/examples` folder of this rep
|
|
60
60
|
- [Class Schema](#class-schemas)
|
61
61
|
- [Definable](#definable)
|
62
62
|
- [Buildable](#buildable)
|
63
|
+
- [Struct](#struct)
|
63
64
|
- [Validation Methods](#validation-methods)
|
64
65
|
- [Validation Rules](#validation-rules)
|
65
66
|
- [validate!](#validate)
|
@@ -103,32 +104,191 @@ require 'dynamic_schema'
|
|
103
104
|
|
104
105
|
### Defining Schemas with **DynamicSchema**
|
105
106
|
|
106
|
-
DynamicSchema
|
107
|
-
|
108
|
-
instantiate `DynamicSchema::Builder`, then call it's `define` method, to prepare a builder.
|
107
|
+
DynamicSchema lets you define a DSL made of values, objects, and options, then reuse that DSL to
|
108
|
+
build and validate Ruby Hashes.
|
109
109
|
|
110
|
-
|
111
|
-
|
110
|
+
You start by constructing a `DynamicSchema::Builder`. You can do this by calling:
|
111
|
+
- `DynamicSchema.define { … }`
|
112
|
+
- `DynamicSchema::Builder.new.define { … }`
|
112
113
|
|
113
|
-
|
114
|
-
using the DSL you've defined. The builder has a 'build' method which will construct a Hash without
|
115
|
-
validating the values. If you've specified that a value should be of a specific type and an
|
116
|
-
incompatible type was given that type will be in the Hash with no indication of that violation.
|
117
|
-
Alterativelly, you can call the `build!` method which will validate the Hash, raising an exception
|
118
|
-
if any of the schema criteria is violated.
|
114
|
+
In both cases, you pass a block that declares the schema values and objects with their options.
|
119
115
|
|
120
|
-
|
121
|
-
|
116
|
+
```ruby
|
117
|
+
schema = DynamicSchema.define do
|
118
|
+
# values with an optional default
|
119
|
+
api_key String
|
120
|
+
model String, default: 'gpt-4o'
|
121
|
+
|
122
|
+
# object with its own values
|
123
|
+
chat_options do
|
124
|
+
max_tokens Integer, default: 1024
|
125
|
+
temperature Float, in: 0..1
|
126
|
+
end
|
127
|
+
end
|
128
|
+
```
|
129
|
+
|
130
|
+
You can then:
|
131
|
+
|
132
|
+
```ruby
|
133
|
+
# build without validation
|
134
|
+
built = schema.build do
|
135
|
+
api_key 'secret'
|
136
|
+
chat_options do
|
137
|
+
temperature 0.7
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
# build with validation (raises on first error)
|
142
|
+
built_validated = schema.build! do
|
143
|
+
api_key 'secret'
|
144
|
+
chat_options do
|
145
|
+
temperature 0.7
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
# validate an existing Hash (no building)
|
150
|
+
errors = schema.validate( { api_key: 'secret', chat_options: { temperature: 0.7 } } )
|
151
|
+
valid = schema.valid?( { api_key: 'secret', chat_options: { temperature: 0.7 } } )
|
152
|
+
```
|
153
|
+
|
154
|
+
#### Inheritance
|
155
|
+
|
156
|
+
You can extend an existing schema using the `inherit:` option. Pass a Proc that describes
|
157
|
+
the parent schema—typically from a class that includes `DynamicSchema::Definable` via
|
158
|
+
its `schema` method.
|
159
|
+
|
160
|
+
```ruby
|
161
|
+
class BaseSettings
|
162
|
+
include DynamicSchema::Definable
|
163
|
+
schema do
|
164
|
+
api_key String, required: true
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
# extend the base schema with additional fields
|
169
|
+
builder = DynamicSchema.define( inherit: BaseSettings.schema ) do
|
170
|
+
region Symbol, in: %i[us eu apac]
|
171
|
+
end
|
172
|
+
|
173
|
+
settings = builder.build! do
|
174
|
+
api_key 'secret'
|
175
|
+
region :us
|
176
|
+
end
|
177
|
+
```
|
178
|
+
|
179
|
+
You can call `build`, `build!`, `validate`, `validate!`, and `valid?` on the builder as needed.
|
180
|
+
|
181
|
+
---
|
182
|
+
|
183
|
+
## Struct
|
184
|
+
|
185
|
+
In addition to building plain Ruby `Hash` values, DynamicSchema can generate lightweight
|
186
|
+
Ruby classes from a schema. A `DynamicSchema::Struct` exposes readers and writers for the
|
187
|
+
fields you define, and transparently wraps nested objects so that you can access them with
|
188
|
+
dot-style accessors rather than deep hash indexing.
|
189
|
+
|
190
|
+
You create a struct class by passing the same schema shape you would give to a Builder. The
|
191
|
+
schema can be provided as:
|
192
|
+
|
193
|
+
- a `Proc` that defines the schema
|
194
|
+
- a `DynamicSchema::Builder`
|
195
|
+
- a compiled `Hash` (advanced)
|
196
|
+
|
197
|
+
```ruby
|
198
|
+
require 'dynamic_schema'
|
199
|
+
|
200
|
+
# simple struct with typed fields
|
201
|
+
Person = DynamicSchema::Struct.define do
|
202
|
+
full_name String
|
203
|
+
age Integer
|
204
|
+
end
|
205
|
+
|
206
|
+
person = Person.build( full_name: 'Sam Lee', age: '42' )
|
207
|
+
person.age # => 42 (coerced using the same converters as Builder)
|
208
|
+
person.full_name = 'Samira Lee'
|
209
|
+
person.to_h # => { full_name: 'Samira Lee', age: 42 }
|
210
|
+
|
211
|
+
# nested object with its own accessors
|
212
|
+
Company = DynamicSchema::Struct.define do
|
213
|
+
employee do
|
214
|
+
full_name String
|
215
|
+
years_of_service Integer
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
acme = Company.build( employee: { full_name: 'Alex', years_of_service: 5 } )
|
220
|
+
acme.employee.full_name # => 'Alex'
|
221
|
+
acme.employee.years_of_service # => 5
|
222
|
+
|
223
|
+
# array of nested objects
|
224
|
+
Order = DynamicSchema::Struct.define do
|
225
|
+
items array: true do
|
226
|
+
name String
|
227
|
+
price Integer
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
order = Order.build( items: [ { name: 'Desk', price: 100 }, { name: 'Chair', price: 50 } ] )
|
232
|
+
order.items.map { | i | i.name } # => [ 'Desk', 'Chair' ]
|
233
|
+
|
234
|
+
# referencing another struct class
|
235
|
+
OrderItem = DynamicSchema::Struct.define do
|
236
|
+
name String
|
237
|
+
quantity Integer
|
238
|
+
end
|
239
|
+
|
240
|
+
OrderCollection = DynamicSchema::Struct.define do
|
241
|
+
order_number String
|
242
|
+
line_items OrderItem, array: true
|
243
|
+
end
|
244
|
+
|
245
|
+
collection = OrderCollection.new( {
|
246
|
+
order_number: 'A-100',
|
247
|
+
line_items: [ { name: 'Desk', quantity: 1 }, { name: 'Chair', quantity: 2 } ]
|
248
|
+
} )
|
249
|
+
collection.line_items[ 0 ].name # => 'Desk'
|
250
|
+
collection.line_items[ 1 ].quantity # => 2
|
251
|
+
```
|
252
|
+
|
253
|
+
- defining
|
254
|
+
- `DynamicSchema::Struct.define` takes a block that looks exactly like a Builder schema.
|
255
|
+
- Use `array: true` to expose arrays of nested structs.
|
256
|
+
- You may reference another struct class as a value type; arrays of that type expose
|
257
|
+
nested accessors for each element.
|
258
|
+
- building
|
259
|
+
- `StructClass.build( attributes )` constructs an instance and (optionally) coerces typed
|
260
|
+
scalar fields using the same converters as the Builder.
|
261
|
+
- `StructClass.build!` additionally validates the instance just like `builder.build!`.
|
262
|
+
- accessing
|
263
|
+
- Use standard Ruby readers/writers: `instance.attribute`, `instance.attribute = value`.
|
264
|
+
- `#to_h` returns a deep Hash of the current values (nested structs become hashes).
|
265
|
+
|
266
|
+
You can also create a struct class from a builder or a compiled hash if you already have a
|
267
|
+
schema elsewhere:
|
268
|
+
|
269
|
+
```ruby
|
270
|
+
builder = DynamicSchema.define do
|
271
|
+
name String
|
272
|
+
end
|
273
|
+
|
274
|
+
NameStruct = DynamicSchema::Struct.new( builder )
|
275
|
+
NameStruct.build( name: 'Taylor' ).name # => 'Taylor'
|
276
|
+
```
|
277
|
+
|
278
|
+
- validation
|
279
|
+
- struct instances include the same validation helpers as hashes built via a builder.
|
280
|
+
- `StructClass.build!` validates immediately and raises on the first error.
|
281
|
+
- instances respond to `#validate!`, `#validate`, and `#valid?` using the compiled schema.
|
122
282
|
|
123
283
|
---
|
124
284
|
|
125
285
|
## Values
|
126
286
|
|
127
|
-
A *value* is
|
128
|
-
options or API
|
287
|
+
A *value* is a basic building block of your schema. Values represent individual settings,
|
288
|
+
options or API parameters that you can define with specific types, defaults, and other options.
|
129
289
|
|
130
290
|
When defining a value, you provide the name as though you were calling a Ruby method, with
|
131
|
-
arguments that include an optional type (which can be a `Class`, `Module` or an `Array` of these
|
291
|
+
arguments that include an optional type (which can be a `Class`, `Module` or an `Array` of these)
|
132
292
|
as well as a `Hash` of options, all of which are optional:
|
133
293
|
|
134
294
|
`name {type} default: {true|false}, required: {true|false}, array: {true|false}, as: {name}, in: {Array|Range}`
|
@@ -158,7 +318,7 @@ puts result[:version] # => "1.0"
|
|
158
318
|
- `api_key` defines a value named `api_key`. Any type can be used to assign the value.
|
159
319
|
- `version, String, default: '1.0'` defines a value with a default.
|
160
320
|
- building
|
161
|
-
- `schema.build!`
|
321
|
+
- `schema.build!` accepts both a Hash and a block where you can set the values.
|
162
322
|
- Inside the block, `api_key 'your-api-key'` sets the value of `api_key`.
|
163
323
|
- accessing
|
164
324
|
- `result[:api_key]` retrieves the value of `api_key`.
|
@@ -388,7 +548,7 @@ result = schema.build! do
|
|
388
548
|
end
|
389
549
|
```
|
390
550
|
|
391
|
-
### :arguments Option
|
551
|
+
### :arguments Option
|
392
552
|
|
393
553
|
The `:arguments` option allows objects to accept arguments when building. Any arguments provided
|
394
554
|
must appear when the object is built ( and so are implicitly 'required' ).
|
@@ -424,7 +584,7 @@ their definition and construction.
|
|
424
584
|
|
425
585
|
### Definable
|
426
586
|
|
427
|
-
The `Definable` module, when
|
587
|
+
The `Definable` module, when included in a class, will add the `schema` and the `builder` class
|
428
588
|
methods.
|
429
589
|
|
430
590
|
By calling `schema` with a block you can define a schema for that specific class. You may also
|
@@ -433,7 +593,7 @@ may be called repeatedly to build up a schema with each call adding to the exist
|
|
433
593
|
( replacing values and objects of the same name if they appear in subsequent calls ).
|
434
594
|
|
435
595
|
The `schema` method will integrate with a class hierarchy. By including Definable in a base class
|
436
|
-
you can call `schema` to define a schema for that base class and then in subsequent
|
596
|
+
you can call `schema` to define a schema for that base class and then in subsequent derived classes
|
437
597
|
to augment it for those classes.
|
438
598
|
|
439
599
|
The `builder` method will return a memoized builder of the schema defined by calls to the `schema`
|
@@ -456,7 +616,7 @@ class DatabaSetting < Setting
|
|
456
616
|
end
|
457
617
|
end
|
458
618
|
|
459
|
-
def
|
619
|
+
def initialize( attributes = {} )
|
460
620
|
# validate the attributes
|
461
621
|
self.class.builder.validate!( attributes )
|
462
622
|
# retain them for future access
|
@@ -468,13 +628,13 @@ end
|
|
468
628
|
|
469
629
|
### Buildable
|
470
630
|
|
471
|
-
The `Buildable` module can be included in a class, in addition to `Definable
|
631
|
+
The `Buildable` module can be included in a class, in addition to `Definable`, to facilitate
|
472
632
|
building that class using a schema assisted builder pattern. The `Buildable` module adds
|
473
633
|
`build!` and `build` methods to the class which can be used to build that class, with and
|
474
|
-
without validation
|
634
|
+
without validation respectively.
|
475
635
|
|
476
|
-
These methods accept both a
|
477
|
-
that can be used to build the class instance. The attributes and block can be used
|
636
|
+
These methods accept both a Hash with attributes that follow the schema, as well as a block
|
637
|
+
that can be used to build the class instance. The attributes and block can be used simultaneously.
|
478
638
|
|
479
639
|
**Important** Note that `Buildable` requires a class method `builder` ( which `Definable`
|
480
640
|
provides ) and an initializer that accepts a `Hash` of attributes.
|
@@ -488,17 +648,17 @@ class Setting
|
|
488
648
|
end
|
489
649
|
end
|
490
650
|
|
491
|
-
class
|
651
|
+
class DatabaseSetting < Setting
|
492
652
|
schema do
|
493
653
|
database do
|
494
|
-
adapter Symbol
|
654
|
+
adapter Symbol
|
495
655
|
host String
|
496
656
|
port String
|
497
657
|
name String
|
498
658
|
end
|
499
659
|
end
|
500
660
|
|
501
|
-
def
|
661
|
+
def initialize( attributes = {} )
|
502
662
|
# validate the attributes
|
503
663
|
self.class.builder.validate!( attributes )
|
504
664
|
# retain them for the future
|
@@ -506,7 +666,7 @@ class DatabaSetting < Setting
|
|
506
666
|
end
|
507
667
|
end
|
508
668
|
|
509
|
-
database_settings =
|
669
|
+
database_settings = DatabaseSetting.build! name: 'settings.database' do
|
510
670
|
database adapter: :pg do
|
511
671
|
host "localhost"
|
512
672
|
port "127.0.0.1"
|
@@ -520,7 +680,7 @@ end
|
|
520
680
|
DynamicSchema provides three different methods for validating Hash structures against your
|
521
681
|
defined schema: `validate!`, `validate`, and `valid?`.
|
522
682
|
|
523
|
-
These methods allow you to verify that your data conforms to your schema
|
683
|
+
These methods allow you to verify that your data conforms to your schema requirements,
|
524
684
|
including type constraints, required fields, and value ranges.
|
525
685
|
|
526
686
|
### Validation Rules
|
@@ -528,13 +688,13 @@ including type constraints, required fields, and value ranges.
|
|
528
688
|
When validating, DynamicSchema checks:
|
529
689
|
|
530
690
|
1. **Required Fields**:
|
531
|
-
Any value or object marked as `required: true`
|
691
|
+
Any value or object marked as `required: true` is present.
|
532
692
|
2. **Type Constraints**:
|
533
693
|
Any values match their specified types or can be coerced to the specified type.
|
534
694
|
3. **Value Ranges**:
|
535
695
|
Any values fall within their specified `:in` constraints.
|
536
696
|
4. **Objects**:
|
537
|
-
Any objects are recursively
|
697
|
+
Any objects are recursively validated.
|
538
698
|
5. **Arrays**:
|
539
699
|
Any validation rules are applied to each element when `array: true`
|
540
700
|
|
@@ -642,7 +802,7 @@ Each error includes helpful context about the validation failure, including the
|
|
642
802
|
|
643
803
|
## Contributing
|
644
804
|
|
645
|
-
Bug reports and pull requests are welcome on GitHub at [https://github.com/EndlessInternational/
|
805
|
+
Bug reports and pull requests are welcome on GitHub at [https://github.com/EndlessInternational/dynamic_schema](https://github.com/EndlessInternational/dynamic_schema).
|
646
806
|
|
647
807
|
## License
|
648
808
|
|
data/dynamicschema.gemspec
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
Gem::Specification.new do | spec |
|
2
2
|
|
3
3
|
spec.name = 'dynamicschema'
|
4
|
-
spec.version = '
|
4
|
+
spec.version = '2.0.0'
|
5
5
|
spec.authors = [ 'Kristoph Cichocki-Romanov' ]
|
6
6
|
spec.email = [ 'rubygems.org@kristoph.net' ]
|
7
7
|
|
@@ -19,6 +19,11 @@ Gem::Specification.new do | spec |
|
|
19
19
|
and validations. By allowing default values, type constraints, nested schemas, and
|
20
20
|
transformations, DynamicSchema ensures that your data structures are both robust and
|
21
21
|
flexible.
|
22
|
+
|
23
|
+
New in 2.0, DynamicSchema adds DynamicSchema::Struct which faciliates effortless definition
|
24
|
+
and construction of complex object hierarchies, with optional type coersion and validation.
|
25
|
+
Where DynamicSchema simplified configuration and API payload construction,
|
26
|
+
DynamicSchema::Struct simplifies construction of complex API reponses.
|
22
27
|
TEXT
|
23
28
|
|
24
29
|
spec.license = 'MIT'
|
@@ -6,15 +6,15 @@ module DynamicSchema
|
|
6
6
|
end
|
7
7
|
|
8
8
|
module ClassMethods
|
9
|
+
[ :build, :build_from_bytes, :build_from_file ].each do | name |
|
10
|
+
define_method( name ) do | *args, **kwargs, &block |
|
11
|
+
new( builder.public_send( name, *args, **kwargs, &block ) )
|
12
|
+
end
|
9
13
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
def build!( attributes = nil, &block )
|
15
|
-
new( builder.build!( attributes, &block ) )
|
16
|
-
end
|
17
|
-
|
14
|
+
define_method( :"#{name}!" ) do | *args, **kwargs, &block |
|
15
|
+
new( builder.public_send( :"#{name}!", *args, **kwargs, &block ) )
|
16
|
+
end
|
17
|
+
end
|
18
18
|
end
|
19
19
|
|
20
20
|
end
|
@@ -1,38 +1,63 @@
|
|
1
|
-
require_relative '
|
2
|
-
require_relative '
|
3
|
-
require_relative 'resolver'
|
4
|
-
require_relative 'receiver'
|
1
|
+
require_relative 'compiler'
|
2
|
+
require_relative 'receiver/object'
|
5
3
|
|
6
4
|
module DynamicSchema
|
7
5
|
class Builder
|
8
|
-
|
9
|
-
include
|
10
|
-
include BuilderMethods::Conversion
|
6
|
+
include Validator
|
7
|
+
include Converter
|
11
8
|
|
12
|
-
def initialize
|
13
|
-
self.
|
14
|
-
|
9
|
+
def initialize
|
10
|
+
self.compiled_schema = nil
|
11
|
+
@schema_blocks = []
|
15
12
|
end
|
16
13
|
|
17
|
-
def define( &block )
|
18
|
-
|
14
|
+
def define( inherit: nil, &block )
|
15
|
+
@schema_blocks << inherit if inherit
|
16
|
+
@schema_blocks << block if block
|
17
|
+
|
18
|
+
compiler = Compiler.new( self.compiled_schema )
|
19
|
+
compiler.compile( &inherit ) if inherit
|
20
|
+
compiler.compile( &block ) if block
|
21
|
+
self.compiled_schema = compiler.compiled
|
19
22
|
self
|
20
23
|
end
|
21
24
|
|
25
|
+
def schema
|
26
|
+
blocks = @schema_blocks.dup
|
27
|
+
proc do
|
28
|
+
blocks.each { | block | instance_eval( &block ) }
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
22
32
|
def build( values = nil, &block )
|
23
|
-
receiver = Receiver.new( values, schema: self.
|
33
|
+
receiver = Receiver::Object.new( values, schema: self.compiled_schema, converter: self )
|
24
34
|
receiver.instance_eval( &block ) if block
|
25
35
|
receiver.to_h
|
26
36
|
end
|
27
37
|
|
28
|
-
def
|
29
|
-
|
30
|
-
|
31
|
-
|
38
|
+
def build_from_bytes( bytes, filename: '(schema)', values: nil )
|
39
|
+
receiver = Receiver::Object.new( values, schema: compiled_schema, converter: self )
|
40
|
+
receiver.instance_eval( bytes, filename, 1 )
|
41
|
+
receiver.to_h
|
42
|
+
end
|
43
|
+
|
44
|
+
def build_from_file( path, values: nil )
|
45
|
+
self.build_from_bytes(
|
46
|
+
File.read( path, encoding: 'UTF-8' ),
|
47
|
+
filename: path, values: values
|
48
|
+
)
|
49
|
+
end
|
50
|
+
|
51
|
+
[ :build, :build_from_bytes, :build_from_file ].each do |name|
|
52
|
+
define_method( :"#{name}!" ) do |*args, **kwargs, &blk|
|
53
|
+
result = public_send(name, *args, **kwargs, &blk)
|
54
|
+
validate!(result)
|
55
|
+
result
|
56
|
+
end
|
32
57
|
end
|
33
58
|
|
34
59
|
private
|
35
|
-
attr_accessor :
|
60
|
+
attr_accessor :compiled_schema
|
36
61
|
|
37
62
|
end
|
38
63
|
end
|
@@ -1,56 +1,57 @@
|
|
1
|
-
require_relative 'receiver'
|
1
|
+
require_relative 'receiver/object'
|
2
2
|
|
3
3
|
module DynamicSchema
|
4
|
-
class
|
4
|
+
class Compiler < BasicObject
|
5
5
|
|
6
|
-
def initialize(
|
7
|
-
@
|
6
|
+
def initialize( compiled_schema = nil, compiled_blocks: nil )
|
7
|
+
@compiled_schema = compiled_schema || {}
|
8
8
|
|
9
9
|
@block = nil
|
10
|
-
@
|
11
|
-
@
|
10
|
+
@compiled = false
|
11
|
+
@compiled_blocks = compiled_blocks || []
|
12
12
|
end
|
13
13
|
|
14
|
-
def
|
14
|
+
def compile( &block )
|
15
15
|
@block = block
|
16
|
-
@
|
17
|
-
unless @
|
18
|
-
@
|
16
|
+
@compiled = false
|
17
|
+
unless @compiled_blocks.include?( @block )
|
18
|
+
@compiled_blocks << @block
|
19
19
|
self.instance_eval( &@block )
|
20
|
-
@
|
20
|
+
@compiled = true
|
21
21
|
end
|
22
22
|
self
|
23
23
|
end
|
24
24
|
|
25
|
-
def
|
26
|
-
|
27
|
-
|
28
|
-
@resolved_blocks << @block unless @resolved_blocks.include?( @block )
|
25
|
+
def compiled
|
26
|
+
if !@compiled && @block
|
27
|
+
@compiled_blocks << @block unless @compiled_blocks.include?( @block )
|
29
28
|
self.instance_eval( &@block )
|
30
|
-
@
|
29
|
+
@compiled = true
|
31
30
|
end
|
32
|
-
@
|
31
|
+
@compiled_schema
|
33
32
|
end
|
34
33
|
|
35
34
|
def _value( name, options )
|
36
35
|
name = name.to_sym
|
36
|
+
receiver = ::DynamicSchema::Receiver::Object
|
37
37
|
::Kernel.raise ::NameError, "The name '#{name}' is reserved and cannot be used for parameters." \
|
38
|
-
if
|
38
|
+
if receiver.method_defined?( name ) || receiver.private_method_defined?( name )
|
39
39
|
|
40
40
|
_validate_in!( name, options[ :type ], options[ :in ] ) if options[ :in ]
|
41
41
|
|
42
|
-
@
|
42
|
+
@compiled_schema[ name ] = options
|
43
43
|
self
|
44
44
|
end
|
45
45
|
|
46
46
|
def _object( name, options = {}, &block )
|
47
47
|
name = name.to_sym
|
48
|
+
receiver = ::DynamicSchema::Receiver::Object
|
48
49
|
::Kernel.raise ::NameError, "The name '#{name}' is reserved and cannot be used for parameters." \
|
49
|
-
if
|
50
|
+
if receiver.method_defined?( name ) || receiver.private_method_defined?( name )
|
50
51
|
|
51
|
-
@
|
52
|
+
@compiled_schema[ name ] = options.merge( {
|
52
53
|
type: ::Object,
|
53
|
-
|
54
|
+
compiler: Compiler.new( compiled_blocks: @compiled_blocks ).compile( &block )
|
54
55
|
} )
|
55
56
|
self
|
56
57
|
end
|
@@ -60,15 +61,11 @@ module DynamicSchema
|
|
60
61
|
options = nil
|
61
62
|
if args.empty?
|
62
63
|
options = {}
|
63
|
-
# when called with just options: name as: :streams
|
64
64
|
elsif first.is_a?( ::Hash )
|
65
65
|
options = first
|
66
|
-
# when called with just type: name String
|
67
|
-
# name [ TrueClass, FalseClass ]
|
68
66
|
elsif args.length == 1 &&
|
69
67
|
( first.is_a?( ::Class ) || first.is_a?( ::Module ) || first.is_a?( ::Array ) )
|
70
68
|
options = { type: first }
|
71
|
-
# when called with just type and options: name String, default: 'the default'
|
72
69
|
elsif args.length == 2 &&
|
73
70
|
( first.is_a?( ::Class ) || first.is_a?( ::Module ) || first.is_a?( ::Array ) ) &&
|
74
71
|
args[ 1 ].is_a?( ::Hash )
|
@@ -81,9 +78,9 @@ module DynamicSchema
|
|
81
78
|
end
|
82
79
|
|
83
80
|
type = options[ :type ]
|
84
|
-
if type == ::Object || type.nil? && block
|
81
|
+
if type == ::Object || ( type.nil? && block )
|
85
82
|
_object( method, options, &block )
|
86
|
-
else
|
83
|
+
else
|
87
84
|
_value( method, options )
|
88
85
|
end
|
89
86
|
|
@@ -94,15 +91,15 @@ module DynamicSchema
|
|
94
91
|
end
|
95
92
|
|
96
93
|
def inspect
|
97
|
-
{ schema: @
|
94
|
+
{ schema: @compiled_schema }.inspect
|
98
95
|
end
|
99
96
|
|
100
97
|
def class
|
101
|
-
::DynamicSchema::
|
98
|
+
::DynamicSchema::Compiler
|
102
99
|
end
|
103
100
|
|
104
101
|
def is_a?( klass )
|
105
|
-
klass == ::DynamicSchema::
|
102
|
+
klass == ::DynamicSchema::Compiler || klass == ::BasicObject
|
106
103
|
end
|
107
104
|
|
108
105
|
alias :kind_of? :is_a?
|
@@ -110,7 +107,7 @@ module DynamicSchema
|
|
110
107
|
if defined?( ::PP )
|
111
108
|
include ::PP::ObjectMixin
|
112
109
|
def pretty_print( pp )
|
113
|
-
pp.pp( { schema: @
|
110
|
+
pp.pp( { schema: @compiled_schema } )
|
114
111
|
end
|
115
112
|
end
|
116
113
|
|
@@ -124,6 +121,3 @@ module DynamicSchema
|
|
124
121
|
|
125
122
|
end
|
126
123
|
end
|
127
|
-
|
128
|
-
|
129
|
-
|