parametric 0.2.17 → 0.2.19
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +133 -36
- data/lib/parametric/field.rb +26 -22
- data/lib/parametric/policy_adapter.rb +57 -0
- data/lib/parametric/schema.rb +2 -0
- data/lib/parametric/tagged_one_of.rb +134 -0
- data/lib/parametric/version.rb +1 -1
- data/parametric.gemspec +1 -1
- data/spec/schema_spec.rb +81 -0
- data/spec/struct_spec.rb +5 -1
- metadata +7 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 98676c8edebba19bd1adff020f5eadf9dce07e44f89125be4f0451d6bdee50cf
|
4
|
+
data.tar.gz: 5a5d0ecded864e5185d2b1d1cafe5eeb2307d92cf52c4111e7dd85ebe65c0321
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1a3b81c816a02516450bdeb70ddb5b804741481ec0084167f31cdddcef4c3734f25b0d69da022048d9889c7b47a462cd821bf3a4cef07d719aff9b57293e10ef
|
7
|
+
data.tar.gz: 5a2cd8dd4d68cd896901d744f656fca3385f6d3763bc05d62dbd1b6c53362a32092781e7bd57e439b27c6e0071d1179a0355372778d33b9c96f893e6fd4e07d3
|
data/README.md
CHANGED
@@ -127,6 +127,60 @@ person_schema = Parametric::Schema.new do |sc, options|
|
|
127
127
|
sc.field(:friends).type(:array).schema(friends_schema)
|
128
128
|
end
|
129
129
|
```
|
130
|
+
|
131
|
+
## Tagged One Of (multiple nested schemas, discriminated by payload key).
|
132
|
+
|
133
|
+
You can use `Field#tagged_one_of` to resolve a nested schema based on the value of a top-level field.
|
134
|
+
|
135
|
+
```ruby
|
136
|
+
user_schema = Parametric::Schema.new do |sc, _|
|
137
|
+
field(:name).type(:string).present
|
138
|
+
field(:age).type(:integer).present
|
139
|
+
end
|
140
|
+
|
141
|
+
company_schema = Parametric::Schema.new do
|
142
|
+
field(:name).type(:string).present
|
143
|
+
field(:company_code).type(:string).present
|
144
|
+
end
|
145
|
+
|
146
|
+
schema = Parametric::Schema.new do |sc, _|
|
147
|
+
# Use :type field to locate the sub-schema to use for :sub
|
148
|
+
sc.field(:type).type(:string)
|
149
|
+
|
150
|
+
# Use the :one_of policy to select the sub-schema based on the :type field above
|
151
|
+
sc.field(:sub).type(:object).tagged_one_of do |sub|
|
152
|
+
sub.index_by(:type)
|
153
|
+
sub.on('user', user_schema)
|
154
|
+
sub.on('company', company_schema)
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
# The schema will now select the correct sub-schema based on the value of :type
|
159
|
+
result = schema.resolve(type: 'user', sub: { name: 'Joe', age: 30 })
|
160
|
+
|
161
|
+
# Instances can also be created separately and used as a policy:
|
162
|
+
|
163
|
+
UserOrCompany = Parametric::TaggedOneOf.new do |sc, _|
|
164
|
+
sc.on('user', user_schema)
|
165
|
+
sc.on('company', company_schema)
|
166
|
+
end
|
167
|
+
|
168
|
+
schema = Parametric::Schema.new do |sc, _|
|
169
|
+
sc.field(:type).type(:string)
|
170
|
+
sc.field(:sub).type(:object).policy(UserOrCompany.index_by(:type))
|
171
|
+
end
|
172
|
+
```
|
173
|
+
|
174
|
+
`#index_by` can take a block to decide what value to resolve schemas by:
|
175
|
+
|
176
|
+
```ruby
|
177
|
+
sc.field(:sub).type(:object).tagged_one_of do |sub|
|
178
|
+
sub.index_by { |payload| payload[:entity_type] }
|
179
|
+
sub.on('user', user_schema)
|
180
|
+
sub.on('company', company_schema)
|
181
|
+
end
|
182
|
+
```
|
183
|
+
|
130
184
|
## Built-in policies
|
131
185
|
|
132
186
|
Type coercions (the `type` method) and validations (the `validate` method) are all _policies_.
|
@@ -285,60 +339,96 @@ field(:currency).policy(:value, 'gbp') # this field always resolves to 'gbp'
|
|
285
339
|
|
286
340
|
## Custom policies
|
287
341
|
|
288
|
-
You can also register your own custom policy objects.
|
342
|
+
You can also register your own custom policy objects.
|
343
|
+
A policy consist of the following:
|
344
|
+
|
345
|
+
* A `PolicyFactory` interface:
|
289
346
|
|
290
347
|
```ruby
|
291
348
|
class MyPolicy
|
292
|
-
#
|
293
|
-
|
294
|
-
|
349
|
+
# Initializer signature is up to you.
|
350
|
+
# These are the arguments passed to the policy when using in a Field,
|
351
|
+
# ex. field(:name).policy(:my_policy, 'arg1', 'arg2')
|
352
|
+
def initialize(arg1, arg2)
|
353
|
+
@arg1, @arg2 = arg1, arg2
|
295
354
|
end
|
296
355
|
|
297
|
-
#
|
298
|
-
|
299
|
-
|
300
|
-
true
|
356
|
+
# @return [Hash]
|
357
|
+
def meta_data
|
358
|
+
{ type: :string }
|
301
359
|
end
|
302
360
|
|
303
|
-
#
|
304
|
-
|
305
|
-
|
361
|
+
# Buld a Policy Runner, which is instantiated
|
362
|
+
# for each field when resolving a schema
|
363
|
+
# @param key [Symbol]
|
364
|
+
# @param value [Any]
|
365
|
+
# @option payload [Hash]
|
366
|
+
# @option context [Parametric::Context]
|
367
|
+
# @return [PolicyRunner]
|
368
|
+
def build(key, value, payload:, context:)
|
369
|
+
MyPolicyRunner.new(key, value, payload, context)
|
370
|
+
end
|
371
|
+
end
|
372
|
+
```
|
373
|
+
|
374
|
+
* A `PolicyRunner` interface.
|
375
|
+
|
376
|
+
```ruby
|
377
|
+
class MyPolicyRunner
|
378
|
+
# Initializer is up to you. See `MyPolicy#build`
|
379
|
+
def initialize(key, value, payload, context)
|
380
|
+
|
306
381
|
end
|
307
382
|
|
308
|
-
#
|
309
|
-
|
383
|
+
# Should this policy run at all?
|
384
|
+
# returning [false] halts the field policy chain.
|
385
|
+
# @return [Boolean]
|
386
|
+
def eligible?
|
310
387
|
true
|
311
388
|
end
|
312
389
|
|
313
|
-
#
|
314
|
-
|
315
|
-
|
390
|
+
# If [false], add [#message] to result errors and halt processing field.
|
391
|
+
# @return [Boolean]
|
392
|
+
def valid?
|
393
|
+
true
|
394
|
+
end
|
395
|
+
|
396
|
+
# Coerce the value, or return as-is.
|
397
|
+
# @return [Any]
|
398
|
+
def value
|
399
|
+
@value
|
400
|
+
end
|
401
|
+
|
402
|
+
# Error message for this policy
|
403
|
+
# @return [String]
|
404
|
+
def message
|
405
|
+
"#{@value} is invalid"
|
316
406
|
end
|
317
407
|
end
|
318
408
|
```
|
319
409
|
|
320
|
-
|
410
|
+
Then register your custom policy factory:
|
321
411
|
|
322
412
|
```ruby
|
323
|
-
Parametric.policy :
|
413
|
+
Parametric.policy :my_polict, MyPolicy
|
324
414
|
```
|
325
415
|
|
326
416
|
And then refer to it by name when declaring your schema fields
|
327
417
|
|
328
418
|
```ruby
|
329
|
-
field(:title).policy(:my_policy)
|
419
|
+
field(:title).policy(:my_policy, 'arg1', 'arg2')
|
330
420
|
```
|
331
421
|
|
332
422
|
You can chain custom policies with other policies.
|
333
423
|
|
334
424
|
```ruby
|
335
|
-
field(:title).required.policy(:my_policy)
|
425
|
+
field(:title).required.policy(:my_policy, 'arg1', 'arg2')
|
336
426
|
```
|
337
427
|
|
338
428
|
Note that you can also register instances.
|
339
429
|
|
340
430
|
```ruby
|
341
|
-
Parametric.policy :my_policy, MyPolicy.new
|
431
|
+
Parametric.policy :my_policy, MyPolicy.new('arg1', 'arg2')
|
342
432
|
```
|
343
433
|
|
344
434
|
For example, a policy that can be configured on a field-by-field basis:
|
@@ -349,27 +439,34 @@ class AddJobTitle
|
|
349
439
|
@job_title = job_title
|
350
440
|
end
|
351
441
|
|
352
|
-
def
|
353
|
-
|
442
|
+
def build(key, value, payload:, context:)
|
443
|
+
Runner.new(@job_title, key, value, payload, context)
|
354
444
|
end
|
355
445
|
|
356
|
-
|
357
|
-
|
358
|
-
true
|
446
|
+
def meta_data
|
447
|
+
{}
|
359
448
|
end
|
360
449
|
|
361
|
-
|
362
|
-
|
363
|
-
"#{value}, #{@job_title}"
|
364
|
-
end
|
450
|
+
class Runner
|
451
|
+
attr_reader :message
|
365
452
|
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
453
|
+
def initialize(job_title, key, value, payload, _context)
|
454
|
+
@job_title = job_title
|
455
|
+
@key, @value, @payload = key, value, payload
|
456
|
+
@message = 'is invalid'
|
457
|
+
end
|
370
458
|
|
371
|
-
|
372
|
-
|
459
|
+
def eligible?
|
460
|
+
true
|
461
|
+
end
|
462
|
+
|
463
|
+
def valid?
|
464
|
+
true
|
465
|
+
end
|
466
|
+
|
467
|
+
def value
|
468
|
+
"#{@value}, #{@job_title}"
|
469
|
+
end
|
373
470
|
end
|
374
471
|
end
|
375
472
|
|
data/lib/parametric/field.rb
CHANGED
@@ -1,7 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'delegate'
|
4
|
-
require
|
4
|
+
require 'parametric/field_dsl'
|
5
|
+
require 'parametric/policy_adapter'
|
6
|
+
require 'parametric/tagged_one_of'
|
5
7
|
|
6
8
|
module Parametric
|
7
9
|
class ConfigurationError < StandardError; end
|
@@ -40,6 +42,10 @@ module Parametric
|
|
40
42
|
end
|
41
43
|
alias_method :type, :policy
|
42
44
|
|
45
|
+
def tagged_one_of(instance = nil, &block)
|
46
|
+
policy(instance || Parametric::TaggedOneOf.new(&block))
|
47
|
+
end
|
48
|
+
|
43
49
|
def schema(sc = nil, &block)
|
44
50
|
sc = (sc ? sc : Schema.new(&block))
|
45
51
|
meta schema: sc
|
@@ -79,20 +85,26 @@ module Parametric
|
|
79
85
|
end
|
80
86
|
|
81
87
|
policies.each do |policy|
|
82
|
-
|
83
|
-
|
84
|
-
if
|
85
|
-
eligible =
|
86
|
-
|
88
|
+
begin
|
89
|
+
pol = policy.build(key, value, payload:, context:)
|
90
|
+
if !pol.eligible?
|
91
|
+
eligible = false
|
92
|
+
if has_default?
|
93
|
+
eligible = true
|
94
|
+
value = default_block.call(key, payload, context)
|
95
|
+
end
|
96
|
+
break
|
97
|
+
else
|
98
|
+
value = pol.value
|
99
|
+
if !pol.valid?
|
100
|
+
eligible = true # eligible, but has errors
|
101
|
+
context.add_error pol.message
|
102
|
+
break # only one error at a time
|
103
|
+
end
|
87
104
|
end
|
105
|
+
rescue StandardError => e
|
106
|
+
context.add_error e.message
|
88
107
|
break
|
89
|
-
else
|
90
|
-
value = resolve_one(policy, value, context)
|
91
|
-
if !policy.valid?(value, key, payload)
|
92
|
-
eligible = true # eligible, but has errors
|
93
|
-
context.add_error policy.message
|
94
|
-
break # only one error at a time
|
95
|
-
end
|
96
108
|
end
|
97
109
|
end
|
98
110
|
|
@@ -107,15 +119,6 @@ module Parametric
|
|
107
119
|
|
108
120
|
attr_reader :registry, :default_block
|
109
121
|
|
110
|
-
def resolve_one(policy, value, context)
|
111
|
-
begin
|
112
|
-
policy.coerce(value, key, context)
|
113
|
-
rescue StandardError => e
|
114
|
-
context.add_error e.message
|
115
|
-
value
|
116
|
-
end
|
117
|
-
end
|
118
|
-
|
119
122
|
def has_default?
|
120
123
|
!!default_block && !meta_data[:skip_default]
|
121
124
|
end
|
@@ -127,6 +130,7 @@ module Parametric
|
|
127
130
|
|
128
131
|
obj = obj.new(*args) if obj.respond_to?(:new)
|
129
132
|
obj = PolicyWithKey.new(obj, key)
|
133
|
+
obj = PolicyAdapter.new(obj) unless obj.respond_to?(:build)
|
130
134
|
|
131
135
|
obj
|
132
136
|
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Parametric
|
4
|
+
# Adapt legacy policies to the new policy interface
|
5
|
+
class PolicyAdapter
|
6
|
+
class PolicyRunner
|
7
|
+
def initialize(policy, key, value, payload, context)
|
8
|
+
@policy, @key, @raw_value, @payload, @context = policy, key, value, payload, context
|
9
|
+
end
|
10
|
+
|
11
|
+
# The PolicyRunner interface
|
12
|
+
# @return [Boolean]
|
13
|
+
def eligible?
|
14
|
+
@policy.eligible?(@raw_value, @key, @payload)
|
15
|
+
end
|
16
|
+
|
17
|
+
# @return [Boolean]
|
18
|
+
def valid?
|
19
|
+
@policy.valid?(value, @key, @payload)
|
20
|
+
end
|
21
|
+
|
22
|
+
# @return [Any]
|
23
|
+
def value
|
24
|
+
@value ||= @policy.coerce(@raw_value, @key, @context)
|
25
|
+
end
|
26
|
+
|
27
|
+
# @return [String]
|
28
|
+
def message
|
29
|
+
@policy.message
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def initialize(policy)
|
34
|
+
@policy = policy
|
35
|
+
end
|
36
|
+
|
37
|
+
# The PolicyFactory interface
|
38
|
+
# Buld a Policy Runner, which is instantiated
|
39
|
+
# for each field when resolving a schema
|
40
|
+
# @param key [Symbol]
|
41
|
+
# @param value [Any]
|
42
|
+
# @option payload [Hash]
|
43
|
+
# @option context [Parametric::Context]
|
44
|
+
# @return [PolicyRunner]
|
45
|
+
def build(key, value, payload:, context:)
|
46
|
+
PolicyRunner.new(@policy, key, value, payload, context)
|
47
|
+
end
|
48
|
+
|
49
|
+
def meta_data
|
50
|
+
@policy.meta_data
|
51
|
+
end
|
52
|
+
|
53
|
+
def key
|
54
|
+
@policy.key
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
data/lib/parametric/schema.rb
CHANGED
@@ -0,0 +1,134 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Parametric
|
4
|
+
# A policy that allows you to select a sub-schema based on a value in the payload.
|
5
|
+
# @example
|
6
|
+
#
|
7
|
+
# user_schema = Parametric::Schema.new do |sc, _|
|
8
|
+
# field(:name).type(:string).present
|
9
|
+
# field(:age).type(:integer).present
|
10
|
+
# end
|
11
|
+
#
|
12
|
+
# company_schema = Parametric::Schema.new do
|
13
|
+
# field(:name).type(:string).present
|
14
|
+
# field(:company_code).type(:string).present
|
15
|
+
# end
|
16
|
+
#
|
17
|
+
# schema = Parametric::Schema.new do |sc, _|
|
18
|
+
# # Use :type field to locate the sub-schema to use for :sub
|
19
|
+
# sc.field(:type).type(:string)
|
20
|
+
#
|
21
|
+
# # Use the :one_of policy to select the sub-schema based on the :type field above
|
22
|
+
# sc.field(:sub).type(:object).tagged_one_of do |sub|
|
23
|
+
# sub.index_by(:type)
|
24
|
+
# sub.on('user', user_schema)
|
25
|
+
# sub.on('company', company_schema)
|
26
|
+
# end
|
27
|
+
# end
|
28
|
+
#
|
29
|
+
# # The schema will now select the correct sub-schema based on the value of :type
|
30
|
+
# result = schema.resolve(type: 'user', sub: { name: 'Joe', age: 30 })
|
31
|
+
#
|
32
|
+
# Instances can also be created separately and used as a policy:
|
33
|
+
# @example
|
34
|
+
#
|
35
|
+
# UserOrCompany = Parametric::TaggedOneOf.new do |sc, _|
|
36
|
+
# sc.on('user', user_schema)
|
37
|
+
# sc.on('company', company_schema)
|
38
|
+
# end
|
39
|
+
#
|
40
|
+
# schema = Parametric::Schema.new do |sc, _|
|
41
|
+
# sc.field(:type).type(:string)
|
42
|
+
# sc.field(:sub).type(:object).policy(UserOrCompany.index_by(:type))
|
43
|
+
# end
|
44
|
+
class TaggedOneOf
|
45
|
+
NOOP_INDEX = ->(payload) { payload }.freeze
|
46
|
+
def initialize(index: NOOP_INDEX, matchers: {}, &block)
|
47
|
+
@index = index
|
48
|
+
@matchers = matchers
|
49
|
+
@configuring = false
|
50
|
+
if block_given?
|
51
|
+
@configuring = true
|
52
|
+
block.call(self)
|
53
|
+
@configuring = false
|
54
|
+
end
|
55
|
+
freeze
|
56
|
+
end
|
57
|
+
|
58
|
+
def index_by(callable = nil, &block)
|
59
|
+
if callable.is_a?(Symbol)
|
60
|
+
key = callable
|
61
|
+
callable = ->(payload) { payload[key] }
|
62
|
+
end
|
63
|
+
index = callable || block
|
64
|
+
if configuring?
|
65
|
+
@index = index
|
66
|
+
else
|
67
|
+
self.class.new(index:, matchers: @matchers)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def on(key, schema)
|
72
|
+
@matchers[key] = schema
|
73
|
+
end
|
74
|
+
|
75
|
+
# The [PolicyFactory] interface
|
76
|
+
def build(key, value, payload:, context:)
|
77
|
+
Runner.new(@index, @matchers, key, value, payload, context)
|
78
|
+
end
|
79
|
+
|
80
|
+
def meta_data
|
81
|
+
{ type: :object, one_of: @matchers }
|
82
|
+
end
|
83
|
+
|
84
|
+
private def configuring?
|
85
|
+
@configuring
|
86
|
+
end
|
87
|
+
|
88
|
+
class Runner
|
89
|
+
def initialize(index, matchers, key, value, payload, context)
|
90
|
+
@matchers = matchers
|
91
|
+
@key = key
|
92
|
+
@raw_value = value
|
93
|
+
@payload = payload
|
94
|
+
@context = context
|
95
|
+
@index_value = index.call(payload)
|
96
|
+
end
|
97
|
+
|
98
|
+
# Should this policy run at all?
|
99
|
+
# returning [false] halts the field policy chain.
|
100
|
+
# @return [Boolean]
|
101
|
+
def eligible?
|
102
|
+
true
|
103
|
+
end
|
104
|
+
|
105
|
+
# If [false], add [#message] to result errors and halt processing field.
|
106
|
+
# @return [Boolean]
|
107
|
+
def valid?
|
108
|
+
has_sub_schema?
|
109
|
+
end
|
110
|
+
|
111
|
+
# Coerce the value, or return as-is.
|
112
|
+
# @return [Any]
|
113
|
+
def value
|
114
|
+
@value ||= has_sub_schema? ? sub_schema.coerce(@raw_value, @key, @context) : @raw_value
|
115
|
+
end
|
116
|
+
|
117
|
+
# Error message for this policy
|
118
|
+
# @return [String]
|
119
|
+
def message
|
120
|
+
"#{@value} is invalid. No sub-schema found for '#{@index_value}'"
|
121
|
+
end
|
122
|
+
|
123
|
+
private
|
124
|
+
|
125
|
+
def has_sub_schema?
|
126
|
+
@matchers.key?(@index_value)
|
127
|
+
end
|
128
|
+
|
129
|
+
def sub_schema
|
130
|
+
@sub_schema ||= @matchers[@index_value]
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
data/lib/parametric/version.rb
CHANGED
data/parametric.gemspec
CHANGED
@@ -18,6 +18,6 @@ Gem::Specification.new do |spec|
|
|
18
18
|
spec.require_paths = ["lib"]
|
19
19
|
|
20
20
|
spec.add_development_dependency "rake"
|
21
|
-
spec.add_development_dependency "rspec", '3.
|
21
|
+
spec.add_development_dependency "rspec", '3.12.0'
|
22
22
|
spec.add_development_dependency "byebug"
|
23
23
|
end
|
data/spec/schema_spec.rb
CHANGED
@@ -251,6 +251,87 @@ describe Parametric::Schema do
|
|
251
251
|
end
|
252
252
|
end
|
253
253
|
|
254
|
+
describe '#tagged_one_of for multiple sub-schemas' do
|
255
|
+
let(:user_schema) do
|
256
|
+
described_class.new do
|
257
|
+
field(:name).type(:string).present
|
258
|
+
field(:age).type(:integer).present
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
let(:company_schema) do
|
263
|
+
described_class.new do
|
264
|
+
field(:name).type(:string).present
|
265
|
+
field(:company_code).type(:string).present
|
266
|
+
end
|
267
|
+
end
|
268
|
+
|
269
|
+
it 'picks the right sub-schema' do
|
270
|
+
schema = described_class.new do |sc, _|
|
271
|
+
sc.field(:type).type(:string)
|
272
|
+
sc.field(:sub).type(:object).tagged_one_of do |sub|
|
273
|
+
sub.index_by(:type)
|
274
|
+
sub.on('user', user_schema)
|
275
|
+
sub.on('company', company_schema)
|
276
|
+
end
|
277
|
+
end
|
278
|
+
|
279
|
+
result = schema.resolve(type: 'user', sub: { name: 'Joe', age: 30 })
|
280
|
+
expect(result.valid?).to be true
|
281
|
+
expect(result.output).to eq({ type: 'user', sub: { name: 'Joe', age: 30 } })
|
282
|
+
|
283
|
+
result = schema.resolve(type: 'company', sub: { name: 'ACME', company_code: 123 })
|
284
|
+
expect(result.valid?).to be true
|
285
|
+
expect(result.output).to eq({ type: 'company', sub: { name: 'ACME', company_code: '123' } })
|
286
|
+
|
287
|
+
result = schema.resolve(type: 'company', sub: { name: nil, company_code: 123 })
|
288
|
+
expect(result.valid?).to be false
|
289
|
+
expect(result.errors['$.sub.name']).not_to be_empty
|
290
|
+
|
291
|
+
result = schema.resolve(type: 'foo', sub: { name: 'ACME', company_code: 123 })
|
292
|
+
expect(result.valid?).to be false
|
293
|
+
end
|
294
|
+
|
295
|
+
it 'can be assigned to instance and reused' do
|
296
|
+
user_or_company = Parametric::TaggedOneOf.new do |sub|
|
297
|
+
sub.on('user', user_schema)
|
298
|
+
sub.on('company', company_schema)
|
299
|
+
end
|
300
|
+
|
301
|
+
schema = described_class.new do |sc, _|
|
302
|
+
sc.field(:type).type(:string)
|
303
|
+
sc.field(:sub).type(:object).tagged_one_of(user_or_company.index_by(:type))
|
304
|
+
end
|
305
|
+
|
306
|
+
result = schema.resolve(type: 'user', sub: { name: 'Joe', age: 30 })
|
307
|
+
expect(result.valid?).to be true
|
308
|
+
expect(result.output).to eq({ type: 'user', sub: { name: 'Joe', age: 30 } })
|
309
|
+
end
|
310
|
+
|
311
|
+
specify '#structure' do
|
312
|
+
user_or_company = Parametric::TaggedOneOf.new do |sub|
|
313
|
+
sub.on('user', user_schema)
|
314
|
+
sub.on('company', company_schema)
|
315
|
+
end
|
316
|
+
|
317
|
+
schema = described_class.new do |sc, _|
|
318
|
+
sc.field(:type).type(:string)
|
319
|
+
sc.field(:sub).type(:object).tagged_one_of(user_or_company.index_by(:type))
|
320
|
+
end
|
321
|
+
|
322
|
+
structure = schema.structure
|
323
|
+
structure.dig(:sub).tap do |sub|
|
324
|
+
expect(sub[:type]).to eq :object
|
325
|
+
expect(sub[:one_of][0][:name][:type]).to eq :string
|
326
|
+
expect(sub[:one_of][0][:name][:required]).to be true
|
327
|
+
expect(sub[:one_of][0][:name][:present]).to be true
|
328
|
+
expect(sub[:one_of][0][:age][:type]).to eq :integer
|
329
|
+
|
330
|
+
expect(sub[:one_of][1][:company_code][:type]).to eq :string
|
331
|
+
end
|
332
|
+
end
|
333
|
+
end
|
334
|
+
|
254
335
|
describe "#ignore" do
|
255
336
|
it "ignores fields" do
|
256
337
|
s1 = described_class.new.ignore(:title, :status) do
|
data/spec/struct_spec.rb
CHANGED
@@ -301,6 +301,9 @@ describe Parametric::Struct do
|
|
301
301
|
|
302
302
|
schema do
|
303
303
|
field(:title).type(:string).present
|
304
|
+
field(:consumption).type(:object).present.schema do
|
305
|
+
field(:type).present.options(%w[aaa bbb])
|
306
|
+
end
|
304
307
|
end
|
305
308
|
end
|
306
309
|
|
@@ -308,9 +311,10 @@ describe Parametric::Struct do
|
|
308
311
|
klass.new!(title: '')
|
309
312
|
rescue Parametric::InvalidStructError => e
|
310
313
|
expect(e.errors['$.title']).not_to be nil
|
314
|
+
expect(e.errors['$.consumption']).not_to be nil
|
311
315
|
end
|
312
316
|
|
313
|
-
valid = klass.new!(title: 'foo')
|
317
|
+
valid = klass.new!(title: 'foo', consumption: { type: 'aaa' })
|
314
318
|
expect(valid.title).to eq 'foo'
|
315
319
|
end
|
316
320
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: parametric
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.19
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ismael Celis
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2023-08-10 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rake
|
@@ -30,14 +30,14 @@ dependencies:
|
|
30
30
|
requirements:
|
31
31
|
- - '='
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: 3.
|
33
|
+
version: 3.12.0
|
34
34
|
type: :development
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
38
|
- - '='
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version: 3.
|
40
|
+
version: 3.12.0
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: byebug
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -77,10 +77,12 @@ files:
|
|
77
77
|
- lib/parametric/field.rb
|
78
78
|
- lib/parametric/field_dsl.rb
|
79
79
|
- lib/parametric/policies.rb
|
80
|
+
- lib/parametric/policy_adapter.rb
|
80
81
|
- lib/parametric/registry.rb
|
81
82
|
- lib/parametric/results.rb
|
82
83
|
- lib/parametric/schema.rb
|
83
84
|
- lib/parametric/struct.rb
|
85
|
+
- lib/parametric/tagged_one_of.rb
|
84
86
|
- lib/parametric/version.rb
|
85
87
|
- parametric.gemspec
|
86
88
|
- spec/custom_block_validator_spec.rb
|
@@ -113,7 +115,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
113
115
|
- !ruby/object:Gem::Version
|
114
116
|
version: '0'
|
115
117
|
requirements: []
|
116
|
-
rubygems_version: 3.
|
118
|
+
rubygems_version: 3.4.18
|
117
119
|
signing_key:
|
118
120
|
specification_version: 4
|
119
121
|
summary: DSL for declaring allowed parameters with options, regexp patern and default
|