parametric 0.2.16 → 0.2.18
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +141 -36
- data/lib/parametric/field.rb +27 -23
- data/lib/parametric/policies.rb +26 -0
- data/lib/parametric/policy_adapter.rb +57 -0
- data/lib/parametric/schema.rb +1 -1
- data/lib/parametric/tagged_one_of.rb +134 -0
- data/lib/parametric/version.rb +1 -1
- data/parametric.gemspec +1 -1
- data/spec/field_spec.rb +10 -0
- data/spec/schema_spec.rb +58 -0
- data/spec/struct_spec.rb +28 -9
- data/spec/validators_spec.rb +7 -0
- 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: 403aac3495b3e90bc32cf01c0229b83497d37413fb43b6180c3dafd0a9ec36b9
|
4
|
+
data.tar.gz: a8396d8ab3831e7c93d1643f96562ff0d5fe1ddaa8794dfe7ed8993bd4732cd5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: dbd04349edf7e8f4777ef7c681ec8f16d2cf570d915088fae04942760415d9fd88123741b68957839b21d7145d97b83b6cf6911fce5f0e2db806d34a304e1a0d
|
7
|
+
data.tar.gz: 9851e0d8f4e5e48d38870027c8a987fbf65f264a8c9dfaafe74080ff463e3783267f605eae09ce65b1f43547a27935ae532fd75120d60823fd24e8bf09bd0c4d
|
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_.
|
@@ -275,62 +329,106 @@ Useful for parsing comma-separated query-string parameters.
|
|
275
329
|
field(:status).policy(:split) # turns "pending,confirmed" into ["pending", "confirmed"]
|
276
330
|
```
|
277
331
|
|
332
|
+
### :value
|
333
|
+
|
334
|
+
A policy to return a static value
|
335
|
+
|
336
|
+
```ruby
|
337
|
+
field(:currency).policy(:value, 'gbp') # this field always resolves to 'gbp'
|
338
|
+
```
|
339
|
+
|
278
340
|
## Custom policies
|
279
341
|
|
280
|
-
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:
|
281
346
|
|
282
347
|
```ruby
|
283
348
|
class MyPolicy
|
284
|
-
#
|
285
|
-
|
286
|
-
|
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
|
287
354
|
end
|
288
355
|
|
289
|
-
#
|
290
|
-
|
291
|
-
|
292
|
-
|
356
|
+
# @return [Hash]
|
357
|
+
def meta_data
|
358
|
+
{ type: :string }
|
359
|
+
end
|
360
|
+
|
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
|
+
|
293
381
|
end
|
294
382
|
|
295
|
-
#
|
296
|
-
|
297
|
-
|
383
|
+
# Should this policy run at all?
|
384
|
+
# returning [false] halts the field policy chain.
|
385
|
+
# @return [Boolean]
|
386
|
+
def eligible?
|
387
|
+
true
|
298
388
|
end
|
299
389
|
|
300
|
-
#
|
301
|
-
|
390
|
+
# If [false], add [#message] to result errors and halt processing field.
|
391
|
+
# @return [Boolean]
|
392
|
+
def valid?
|
302
393
|
true
|
303
394
|
end
|
304
395
|
|
305
|
-
#
|
306
|
-
|
307
|
-
|
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"
|
308
406
|
end
|
309
407
|
end
|
310
408
|
```
|
311
409
|
|
312
|
-
|
410
|
+
Then register your custom policy factory:
|
313
411
|
|
314
412
|
```ruby
|
315
|
-
Parametric.policy :
|
413
|
+
Parametric.policy :my_polict, MyPolicy
|
316
414
|
```
|
317
415
|
|
318
416
|
And then refer to it by name when declaring your schema fields
|
319
417
|
|
320
418
|
```ruby
|
321
|
-
field(:title).policy(:my_policy)
|
419
|
+
field(:title).policy(:my_policy, 'arg1', 'arg2')
|
322
420
|
```
|
323
421
|
|
324
422
|
You can chain custom policies with other policies.
|
325
423
|
|
326
424
|
```ruby
|
327
|
-
field(:title).required.policy(:my_policy)
|
425
|
+
field(:title).required.policy(:my_policy, 'arg1', 'arg2')
|
328
426
|
```
|
329
427
|
|
330
428
|
Note that you can also register instances.
|
331
429
|
|
332
430
|
```ruby
|
333
|
-
Parametric.policy :my_policy, MyPolicy.new
|
431
|
+
Parametric.policy :my_policy, MyPolicy.new('arg1', 'arg2')
|
334
432
|
```
|
335
433
|
|
336
434
|
For example, a policy that can be configured on a field-by-field basis:
|
@@ -341,27 +439,34 @@ class AddJobTitle
|
|
341
439
|
@job_title = job_title
|
342
440
|
end
|
343
441
|
|
344
|
-
def
|
345
|
-
|
442
|
+
def build(key, value, payload:, context:)
|
443
|
+
Runner.new(@job_title, key, value, payload, context)
|
346
444
|
end
|
347
445
|
|
348
|
-
|
349
|
-
|
350
|
-
true
|
446
|
+
def meta_data
|
447
|
+
{}
|
351
448
|
end
|
352
449
|
|
353
|
-
|
354
|
-
|
355
|
-
"#{value}, #{@job_title}"
|
356
|
-
end
|
450
|
+
class Runner
|
451
|
+
attr_reader :message
|
357
452
|
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
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
|
362
458
|
|
363
|
-
|
364
|
-
|
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
|
365
470
|
end
|
366
471
|
end
|
367
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
|
@@ -61,7 +67,7 @@ module Parametric
|
|
61
67
|
|
62
68
|
def visit(meta_key = nil, &visitor)
|
63
69
|
if sc = meta_data[:schema]
|
64
|
-
r = sc.visit(meta_key, &visitor)
|
70
|
+
r = sc.schema.visit(meta_key, &visitor)
|
65
71
|
(meta_data[:type] == :array) ? [r] : r
|
66
72
|
else
|
67
73
|
meta_key ? meta_data[meta_key] : yield(self)
|
@@ -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
|
data/lib/parametric/policies.rb
CHANGED
@@ -26,6 +26,31 @@ module Parametric
|
|
26
26
|
{}
|
27
27
|
end
|
28
28
|
end
|
29
|
+
|
30
|
+
class Value
|
31
|
+
attr_reader :message
|
32
|
+
|
33
|
+
def initialize(val, msg = 'invalid value')
|
34
|
+
@message = msg
|
35
|
+
@val = val
|
36
|
+
end
|
37
|
+
|
38
|
+
def eligible?(value, key, payload)
|
39
|
+
payload.key?(key)
|
40
|
+
end
|
41
|
+
|
42
|
+
def coerce(_value, _key, _context)
|
43
|
+
@val
|
44
|
+
end
|
45
|
+
|
46
|
+
def valid?(value, key, payload)
|
47
|
+
!payload.key?(key) || !!(value == @val)
|
48
|
+
end
|
49
|
+
|
50
|
+
def meta_data
|
51
|
+
{ value: @val }
|
52
|
+
end
|
53
|
+
end
|
29
54
|
end
|
30
55
|
|
31
56
|
# Default validators
|
@@ -33,6 +58,7 @@ module Parametric
|
|
33
58
|
|
34
59
|
Parametric.policy :format, Policies::Format
|
35
60
|
Parametric.policy :email, Policies::Format.new(EMAIL_REGEXP, 'invalid email')
|
61
|
+
Parametric.policy :value, Policies::Value
|
36
62
|
|
37
63
|
Parametric.policy :noop do
|
38
64
|
eligible do |value, key, payload|
|
@@ -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/field_spec.rb
CHANGED
@@ -264,6 +264,16 @@ describe Parametric::Field do
|
|
264
264
|
end
|
265
265
|
end
|
266
266
|
|
267
|
+
describe ":value policy" do
|
268
|
+
it 'always resolves to static value' do
|
269
|
+
resolve(subject.policy(:value, 'hello'), a_key: "tag1,tag2").tap do |r|
|
270
|
+
expect(r.eligible?).to be true
|
271
|
+
no_errors
|
272
|
+
expect(r.value).to eq 'hello'
|
273
|
+
end
|
274
|
+
end
|
275
|
+
end
|
276
|
+
|
267
277
|
describe ":declared policy" do
|
268
278
|
it "is eligible if key exists" do
|
269
279
|
resolve(subject.policy(:declared).present, a_key: "").tap do |r|
|
data/spec/schema_spec.rb
CHANGED
@@ -251,6 +251,64 @@ 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
|
+
end
|
311
|
+
|
254
312
|
describe "#ignore" do
|
255
313
|
it "ignores fields" do
|
256
314
|
s1 = described_class.new.ignore(:title, :status) do
|
data/spec/struct_spec.rb
CHANGED
@@ -2,8 +2,8 @@ require 'spec_helper'
|
|
2
2
|
require 'parametric/struct'
|
3
3
|
|
4
4
|
describe Parametric::Struct do
|
5
|
-
|
6
|
-
|
5
|
+
let(:friend_class) do
|
6
|
+
Class.new do
|
7
7
|
include Parametric::Struct
|
8
8
|
|
9
9
|
schema do
|
@@ -11,16 +11,19 @@ describe Parametric::Struct do
|
|
11
11
|
field(:age).type(:integer)
|
12
12
|
end
|
13
13
|
end
|
14
|
+
end
|
15
|
+
let(:klass) do
|
16
|
+
Class.new.tap do |cl|
|
17
|
+
cl.send(:include, Parametric::Struct)
|
14
18
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
schema do
|
19
|
-
field(:title).type(:string).present
|
20
|
-
field(:friends).type(:array).default([]).schema friend_class
|
19
|
+
cl.schema do |sc, _|
|
20
|
+
sc.field(:title).type(:string).present
|
21
|
+
sc.field(:friends).type(:array).default([]).schema friend_class
|
21
22
|
end
|
22
23
|
end
|
24
|
+
end
|
23
25
|
|
26
|
+
it "works" do
|
24
27
|
new_instance = klass.new
|
25
28
|
expect(new_instance.title).to eq ''
|
26
29
|
expect(new_instance.friends).to eq []
|
@@ -57,6 +60,18 @@ describe Parametric::Struct do
|
|
57
60
|
expect(invalid_instance.friends[1].errors['$.name']).not_to be_nil
|
58
61
|
end
|
59
62
|
|
63
|
+
it 'supports #structure' do
|
64
|
+
st = klass.schema.structure
|
65
|
+
expect(st[:title][:type]).to eq(:string)
|
66
|
+
expect(st[:friends][:structure][:age][:type]).to eq(:integer)
|
67
|
+
end
|
68
|
+
|
69
|
+
it 'supports #walk' do
|
70
|
+
output = klass.schema.walk(:type).output
|
71
|
+
expect(output[:title]).to eq(:string)
|
72
|
+
expect(output[:friends][0][:name]).to eq(:string)
|
73
|
+
end
|
74
|
+
|
60
75
|
it "is inmutable by default" do
|
61
76
|
klass = Class.new do
|
62
77
|
include Parametric::Struct
|
@@ -286,6 +301,9 @@ describe Parametric::Struct do
|
|
286
301
|
|
287
302
|
schema do
|
288
303
|
field(:title).type(:string).present
|
304
|
+
field(:consumption).type(:object).present.schema do
|
305
|
+
field(:type).present.options(%w[aaa bbb])
|
306
|
+
end
|
289
307
|
end
|
290
308
|
end
|
291
309
|
|
@@ -293,9 +311,10 @@ describe Parametric::Struct do
|
|
293
311
|
klass.new!(title: '')
|
294
312
|
rescue Parametric::InvalidStructError => e
|
295
313
|
expect(e.errors['$.title']).not_to be nil
|
314
|
+
expect(e.errors['$.consumption']).not_to be nil
|
296
315
|
end
|
297
316
|
|
298
|
-
valid = klass.new!(title: 'foo')
|
317
|
+
valid = klass.new!(title: 'foo', consumption: { type: 'aaa' })
|
299
318
|
expect(valid.title).to eq 'foo'
|
300
319
|
end
|
301
320
|
end
|
data/spec/validators_spec.rb
CHANGED
@@ -9,6 +9,13 @@ describe 'default validators' do
|
|
9
9
|
expect(validator.valid?(payload[key], key, payload)).to eq valid
|
10
10
|
end
|
11
11
|
|
12
|
+
describe ':value' do
|
13
|
+
it {
|
14
|
+
test_validator({key: 'Foobar'}, :key, :value, true, true, 'Foobar')
|
15
|
+
test_validator({key: 'Nope'}, :key, :value, true, false, 'Foobar')
|
16
|
+
}
|
17
|
+
end
|
18
|
+
|
12
19
|
describe ':format' do
|
13
20
|
it {
|
14
21
|
test_validator({key: 'Foobar'}, :key, :format, true, true, /^Foo/)
|
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.18
|
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
|