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 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