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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2f6f417ecf3ae9196ec9dc4757df98b21716c71ee43909e7f47a12d0885c983a
4
- data.tar.gz: aae3258b0c564f98554a6de742aec9f25279c4352180c09b109add7587ab2977
3
+ metadata.gz: 403aac3495b3e90bc32cf01c0229b83497d37413fb43b6180c3dafd0a9ec36b9
4
+ data.tar.gz: a8396d8ab3831e7c93d1643f96562ff0d5fe1ddaa8794dfe7ed8993bd4732cd5
5
5
  SHA512:
6
- metadata.gz: fffc57a0087500348370a0f4fe71744076eed77c9493e16638e21a457af66bc381865630bee84374c164b41909396e150e2e991480f97b2d8cac2017e512b84c
7
- data.tar.gz: 8d24b5272d916ea58a1aa1f425d0261d948fe2c83957ec39a4156795de636f8c9a8e0e2fce1e166bb0a0f669c1ead5d864a65295b50ad7fb1f1d5077a450dc48
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. A policy must implement the following methods:
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
- # Validation error message, if invalid
285
- def message
286
- 'is invalid'
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
- # Whether or not to validate and coerce this value
290
- # if false, no other policies will be run on the field
291
- def eligible?(value, key, payload)
292
- true
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
- # Transform the value
296
- def coerce(value, key, context)
297
- value
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
- # Is the value valid?
301
- def valid?(value, key, payload)
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
- # merge this object into the field's meta data
306
- def meta_data
307
- {type: :string}
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
- You can register your policy with:
410
+ Then register your custom policy factory:
313
411
 
314
412
  ```ruby
315
- Parametric.policy :my_policy, MyPolicy
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 message
345
- 'is invalid'
442
+ def build(key, value, payload:, context:)
443
+ Runner.new(@job_title, key, value, payload, context)
346
444
  end
347
445
 
348
- # Noop
349
- def eligible?(value, key, payload)
350
- true
446
+ def meta_data
447
+ {}
351
448
  end
352
449
 
353
- # Add job title to value
354
- def coerce(value, key, context)
355
- "#{value}, #{@job_title}"
356
- end
450
+ class Runner
451
+ attr_reader :message
357
452
 
358
- # Noop
359
- def valid?(value, key, payload)
360
- true
361
- end
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
- def meta_data
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
 
@@ -1,7 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'delegate'
4
- require "parametric/field_dsl"
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
- if !policy.eligible?(value, key, payload)
83
- eligible = false
84
- if has_default?
85
- eligible = true
86
- value = default_block.call(key, payload, context)
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
@@ -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
@@ -75,7 +75,7 @@ module Parametric
75
75
  fields.each_with_object({}) do |(_, field), obj|
76
76
  meta = field.meta_data.dup
77
77
  sc = meta.delete(:schema)
78
- meta[:structure] = sc.structure if sc
78
+ meta[:structure] = sc.schema.structure if sc
79
79
  obj[field.key] = meta
80
80
  end
81
81
  end
@@ -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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Parametric
4
- VERSION = '0.2.16'
4
+ VERSION = '0.2.18'
5
5
  end
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.4.0'
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
- it "works" do
6
- friend_class = Class.new do
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
- klass = Class.new do
16
- include Parametric::Struct
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
@@ -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.16
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: 2022-03-09 00:00:00.000000000 Z
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.4.0
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.4.0
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.2.32
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