parametric 0.2.16 → 0.2.18
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 +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
|