nxt_schema 1.0.0 → 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (32) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +3 -3
  3. data/README.md +80 -38
  4. data/lib/nxt_schema.rb +17 -17
  5. data/lib/nxt_schema/dsl.rb +7 -7
  6. data/lib/nxt_schema/{application.rb → node.rb} +1 -1
  7. data/lib/nxt_schema/node/any_of.rb +21 -33
  8. data/lib/nxt_schema/node/base.rb +70 -171
  9. data/lib/nxt_schema/node/collection.rb +43 -8
  10. data/lib/nxt_schema/{application → node}/error_store.rb +5 -5
  11. data/lib/nxt_schema/{application → node}/errors/schema_error.rb +1 -1
  12. data/lib/nxt_schema/{application → node}/errors/validation_error.rb +1 -1
  13. data/lib/nxt_schema/node/leaf.rb +7 -5
  14. data/lib/nxt_schema/node/schema.rb +101 -8
  15. data/lib/nxt_schema/template/any_of.rb +50 -0
  16. data/lib/nxt_schema/template/base.rb +218 -0
  17. data/lib/nxt_schema/template/collection.rb +23 -0
  18. data/lib/nxt_schema/{node → template}/has_sub_nodes.rb +16 -10
  19. data/lib/nxt_schema/template/leaf.rb +13 -0
  20. data/lib/nxt_schema/{node → template}/maybe_evaluator.rb +1 -1
  21. data/lib/nxt_schema/{node → template}/on_evaluator.rb +1 -1
  22. data/lib/nxt_schema/template/schema.rb +22 -0
  23. data/lib/nxt_schema/{node → template}/sub_nodes.rb +1 -1
  24. data/lib/nxt_schema/{node → template}/type_resolver.rb +2 -2
  25. data/lib/nxt_schema/{node → template}/type_system_resolver.rb +1 -1
  26. data/lib/nxt_schema/version.rb +1 -1
  27. metadata +17 -17
  28. data/lib/nxt_schema/application/any_of.rb +0 -40
  29. data/lib/nxt_schema/application/base.rb +0 -116
  30. data/lib/nxt_schema/application/collection.rb +0 -57
  31. data/lib/nxt_schema/application/leaf.rb +0 -15
  32. data/lib/nxt_schema/application/schema.rb +0 -114
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 36a741174bf1669b013e32f2b3693c99cae2b4e479378cb1494069dd66f1af44
4
- data.tar.gz: 2984ab8830a495bf1a694cd369216dee00e0dc5cc2cf1c87c173dd5c1e984963
3
+ metadata.gz: d1aba2ccbd135921f48a84de3e51d11b0592522b8d9e9e9fd3a5ff188117ffae
4
+ data.tar.gz: 1c67ef1beda4a6e6a33bd67bc7a9294c7bfbc14ccf72be3b621748212192c8c0
5
5
  SHA512:
6
- metadata.gz: f1924005010c80140b9d545fb307ef373cb237a31e166195d6e959af56b41208d49db1d48ff3ab68c77ad45bdee3941821e9c2584cfe50cd2da47134a15a1eac
7
- data.tar.gz: '001459e22044cfef5aded2ade7d9b8b0fe15c944c4bb886a992578a82886777c2a92e5ce1711919414b79910577b68671249b8bdc74526a89ee0c1437ba4c421'
6
+ metadata.gz: 34cd6cdacddb969ac3532dc8f52150dc16e8be80b12bf8ceaec9cd147df93547cc02e655eaff1d05eee359b660e1143187adb704fbac91a3f09d4a18ebd28087
7
+ data.tar.gz: 5f77d02ed8b601aa255a4b9be082c48f1da0e7fa157561547e7c99a726c8a9d2f34a4c76be78cbd60ec529bce719e0b06e42d6645cbbe18e65facf7de9e7a1fc
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- nxt_schema (1.0.0)
4
+ nxt_schema (1.0.1)
5
5
  activesupport
6
6
  dry-types
7
7
  nxt_init
@@ -50,7 +50,7 @@ GEM
50
50
  minitest (5.14.2)
51
51
  nxt_init (0.1.5)
52
52
  activesupport
53
- nxt_registry (0.3.2)
53
+ nxt_registry (0.3.3)
54
54
  activesupport
55
55
  pry (0.13.1)
56
56
  coderay (~> 1.1)
@@ -72,7 +72,7 @@ GEM
72
72
  thread_safe (0.3.6)
73
73
  tzinfo (1.2.8)
74
74
  thread_safe (~> 0.1)
75
- zeitwerk (2.4.1)
75
+ zeitwerk (2.4.2)
76
76
 
77
77
  PLATFORMS
78
78
  ruby
data/README.md CHANGED
@@ -16,10 +16,13 @@ Or install it yourself as:
16
16
 
17
17
  $ gem install nxt_schema
18
18
 
19
- ## What it is for?
19
+ ## What is it for?
20
20
 
21
- NxtSchema is a type casting and validation framework that allows you to validate and type cast arbitrary nested
22
- structures of data.
21
+ NxtSchema is a type coercion and validation framework that allows you to coerce and validate arbitrary nested
22
+ structures of data. The original idea is taken from https://dry-rb.org/gems/dry-schema and
23
+ https://dry-rb.org/gems/dry-validation from the amazing dry.rb eco system. In contrast to dry-schema,
24
+ NxtSchema aims to be a simpler solution that hopefully is easier to understand and debug.
25
+ It also ships with some handy features that dry-schema does not implement.
23
26
 
24
27
  ### Usage
25
28
 
@@ -31,7 +34,7 @@ PERSON = NxtSchema.schema(:person) do
31
34
  end
32
35
 
33
36
  input = {
34
- first_name: 'Andy',
37
+ first_name: 'Ändy',
35
38
  last_name: 'Robecke',
36
39
  email: 'andreas@robecke.de'
37
40
  }
@@ -58,9 +61,9 @@ The kind of node dictates how the schema is applied to the input. On the root le
58
61
  to create schemas:
59
62
 
60
63
  ```ruby
61
- NxtSchema.schema { ... } # => Create a schema node
62
- NxtSchema.collection { ... } # => Create an array of nodes
63
- NxtSchema.any_of { ... } # => Create a collection of allowed schemas
64
+ NxtSchema.schema { ... } # => Creates a schema node
65
+ NxtSchema.collection { ... } # => Creates an array of nodes
66
+ NxtSchema.any_of { ... } # => Creates a collection of allowed schemas
64
67
  ```
65
68
 
66
69
  #### Node predicate aliases
@@ -85,6 +88,8 @@ into a schema you also have to combine the node predicates with default value me
85
88
  check out the examples below:
86
89
 
87
90
  ```ruby
91
+ # Optional node without default value
92
+
88
93
  schema = NxtSchema.schema(:person) do
89
94
  optional(:email, :String)
90
95
  end
@@ -99,19 +104,24 @@ result.output # => {}
99
104
  ```
100
105
 
101
106
  ```ruby
107
+ # Optional node with default value
108
+
102
109
  schema = NxtSchema.schema(:person) do
103
110
  optional(:email, :String).default('andreas@robecke.de')
104
111
  end
105
112
 
106
113
  result = schema.apply(input: { email: nil })
107
114
  result.errors # => {}
115
+ result.output # => {:email=>"andreas@robecke.de"}
108
116
 
109
117
  result = schema.apply(input: {})
110
118
  result.errors # => {}
111
- result.output # => {:email=>"andreas@robecke.de"}
119
+ result.output # => {}
112
120
  ```
113
121
 
114
122
  ```ruby
123
+ # Omnipresent node without default value
124
+
115
125
  schema = NxtSchema.schema(:person) do
116
126
  omnipresent(:email, :String)
117
127
  end
@@ -122,8 +132,9 @@ result.output # => {:email=>NxtSchema::MissingInput}
122
132
  ```
123
133
 
124
134
  ```ruby
135
+ # Omnipresent node with default value and maybe expression to allow default value to break type contract.
136
+
125
137
  schema = NxtSchema.schema(:person) do
126
- # make sure a node is always present and at least nil even though the type is String
127
138
  omnipresent(:email, :String).default(nil).maybe(:nil?)
128
139
  end
129
140
 
@@ -131,15 +142,16 @@ result = schema.apply(input: {})
131
142
  result.errors # => {}
132
143
  result.output # => {:email=>nil}
133
144
 
134
- result = schema.apply(input: { email: 'andreas@robecke.de' })
145
+ result = schema.apply(input: { email: 'andreas@robecke.de' })
135
146
  result.errors # => {}
136
147
  result.output # => {:email=>"andreas@robecke.de"}
137
148
  ```
138
149
 
139
150
  ##### Conditionally optional nodes
140
151
 
141
- You can also pass a proc as the optional option. This will add a validator to the parent node that makes sure thar the
142
- key is present if the optional condition does not apply.
152
+ You can also pass a proc as the optional option. This is a shortcut for adding a validation to the parent node
153
+ that will result in a validation error in case the optional condition does not apply and the parent node does not
154
+ contain a sub node with that name (here contact schema not including an email node).
143
155
 
144
156
  ```ruby
145
157
  schema = NxtSchema.schema(:contact) do
@@ -180,10 +192,6 @@ The type system is built with dry-types from the amazing https://dry-rb.org eco
180
192
  offers features such as default values for types as well as maybe types, these features are built directly into
181
193
  NxtSchema.
182
194
 
183
- Please note that Dry.rb also has a gem for schemas: https://dry-rb.org/gems/dry-schema and another one dedicated to
184
- validations explicitly https://dry-rb.org/gems/dry-validation. You should probably go and check those out! NxtSchema
185
- is trying to implement a simpler solution that is easy to understand yet powerful enough for most tasks.
186
-
187
195
  In NxtSchema every node has a type and you can either provide a symbol that will be resolved
188
196
  through the type system of the schema or you can directly provide an instance of dry type and thus use your
189
197
  custom types. This means you can basically build any kind of objects such as structs and models from your data and
@@ -197,11 +205,11 @@ type system that was specified NxtSchema will always fallback to nominal types.
197
205
  a separate type system per node if that's what you need.
198
206
 
199
207
  ```ruby
200
- NxtSchema.root do
208
+ NxtSchema.schema do
201
209
  required(:test, :String) # The :String will resolve to NxtSchema::Types::Nominal::String
202
210
  end
203
211
 
204
- NxtSchema.root(type_system: NxtSchema::Types::JSON) do
212
+ NxtSchema.schema(type_system: NxtSchema::Types::JSON) do
205
213
  required(:test, :Date) # The :Date will resolve to NxtSchema::Types::JSON::Date
206
214
  # When the type does not exist in the default type system (there is non JSON::String) we fallback to nominal types
207
215
  required(:test, :String)
@@ -234,7 +242,7 @@ NxtSchema.register_type(
234
242
 
235
243
  # once registered you can use the type in your schema
236
244
 
237
- NxtSchema.root(:company) do
245
+ NxtSchema.schema(:company) do
238
246
  required(:name, :MyCustomStrippedString)
239
247
  end
240
248
  ```
@@ -244,21 +252,21 @@ end
244
252
  #### Default values
245
253
 
246
254
  ```ruby
247
- # Define default values as options or with the default method
255
+ # Define default values with the default method
256
+ required(:test, :DateTime).default(nil)
248
257
  required(:test, :DateTime).default(-> { Time.current })
249
- required(:test, :String, default: 'Andy')
250
258
  ```
251
259
 
252
260
  #### Maybe values
253
261
 
254
- With maybe you can allow your values to be of a certain type and halt conversion. **Note: This means that your output
255
- will simply be set to the input without coercing the value!**
262
+ With maybe expressions you can halt coercion and allow your values to break the type contract.
263
+ **Note: This means that your output will simply be set to the input without coercing the value!**
256
264
 
257
265
  ```ruby
258
266
  # Define maybe values (values that do not match the type)
259
267
  required(:test, :String).maybe(:nil?)
260
268
 
261
- nodes(:tests).maybe(:empty?) do # will allow the collection to be empty
269
+ nodes(:tests).maybe(:empty?) do # will allow the collection to be empty and thus not contain strings
262
270
  required(:test, :String)
263
271
  end
264
272
 
@@ -272,6 +280,8 @@ based on some condition. When the node is yielded to your validation proc you ha
272
280
  `node.input` and `node.index` when the node is within a collection of nodes as well as `node.name`. Furthermore you have
273
281
  access to the context that was passed in when defining the schema or passed to the apply method later.
274
282
 
283
+ **NOTE: Validations only run when no maybe expression applies and the node input could be coerced successfully**
284
+
275
285
  ```ruby
276
286
  # Simple custom validation
277
287
  required(:test, :String).validate(-> (node) { node.add_error("#{node.input} is not valid") if node.input == 'not allowed' })
@@ -320,7 +330,7 @@ end
320
330
  NxtSchema.register_validator(MyCustomExclusionValidator, :my_custom_exclusion_validator)
321
331
 
322
332
  # and then simply reference it with the key you've registered it
323
- schema = NxtSchema.root(:company) do
333
+ schema = NxtSchema.schema(:company) do
324
334
  requires(:name, :String).validate(:my_custom_exclusion_validator, %w[lemonade])
325
335
  end
326
336
 
@@ -333,25 +343,24 @@ schema.apply(name: 'lemonade').valid? # => false
333
343
  - Add translated errors
334
344
  - Interpolate with actual vs. expected
335
345
 
336
- #### Combining validators with custom logic
346
+ #### Combining validators
337
347
 
338
348
  `node(:test, String).validate(...)` basically adds a validator to the node. Of course you can add multiple validators.
339
- But that means that they will all be executed and errors aggregated. If you want your validator to only run in case
349
+ But that means that they will all be executed. If you want your validator to only run in case
340
350
  another was false, you can use `:validat_with do ... end` in order to combine validators based on custom logic.
341
351
 
342
352
  ```ruby
343
- NxtSchema.root do
353
+ NxtSchema.schema do
344
354
  required(:test, :Integer).validate_with do
345
355
  validator(:greater_than, 5) &&
346
- validator(:greater_than, 6) &&
356
+ validator(:greater_than, 6) ||
347
357
  validator(:greater_than, 7)
348
358
  end
349
359
  end
350
360
  ```
351
361
 
352
- This has one drawback however. Let's say your test value is 4. This would only run your first validator and then exit
353
- from the logic since validators are combined with &&. In this example it might not make much sense, but it basically
354
- means that you might not have the full validation errors when combining validations with `:validate_with`
362
+ Note that this will not run subsequent validators once one was valuated to false and thus might not contain all error
363
+ messages of all validators that would have failed.
355
364
 
356
365
 
357
366
  ### Schema options
@@ -363,17 +372,17 @@ You can change this behaviour by providing a strategy for the `:additional_keys`
363
372
 
364
373
  ```ruby
365
374
  # This will simply ignore any other key except test
366
- NxtSchema.root(additional_keys: :ignore) do
375
+ NxtSchema.schema(additional_keys: :ignore) do
367
376
  required(:test, :String)
368
377
  end
369
378
 
370
379
  # This would give you an error in case you apply anything other than { test: '...' }
371
- NxtSchema.root(additional_keys: :restrict) do
380
+ NxtSchema.schema(additional_keys: :restrict) do
372
381
  required(:test, :String)
373
382
  end
374
383
 
375
384
  # This will merge other keys into your output
376
- schema = NxtSchema.root(additional_keys: :allow) do
385
+ schema = NxtSchema.schema(additional_keys: :allow) do
377
386
  required(:test, :String)
378
387
  end
379
388
 
@@ -388,7 +397,7 @@ You may want to transform the keys from your input. Therefore specify the transf
388
397
  when you want your schema to return only symbolized keys for example.
389
398
 
390
399
  ```ruby
391
- schema = NxtSchema.root(transform_keys: ->(key) { key.to_sym}) do
400
+ schema = NxtSchema.schema(transform_keys: ->(key) { key.to_sym}) do
392
401
  required(:test, :String)
393
402
  end
394
403
 
@@ -402,7 +411,7 @@ You want to give nodes an ID or some other meta data? You can use the meta metho
402
411
  information onto any node.
403
412
 
404
413
  ```ruby
405
- schema = NxtSchema.root do
414
+ schema = NxtSchema.schema do
406
415
  ERROR_MESSAGES = {
407
416
  test: 'This is always broken'
408
417
  }
@@ -416,12 +425,43 @@ schema.error # {"root.test"=>["This is always broken"]}
416
425
 
417
426
  #### Contexts
418
427
 
419
- # TODO
428
+ When defining a schema it is possible to pass in a context option. This can be anything that you would like to access
429
+ during building your schema. A context could provide custom validators or default values depending of the name of your
430
+ nodes for instance.
420
431
 
421
432
  ##### Build time
422
433
 
434
+ ```ruby
435
+ context = OpenStruct.new(email_validator: ->(node) { node.input && node.input.includes?('@') })
436
+
437
+ NxtSchema.schema(:developers, context: context) do
438
+ required(:first_name, :String)
439
+ required(:last_name, :String)
440
+ required(:email, :String).validate(context.email_validator)
441
+ end
442
+ ```
443
+
423
444
  ##### Apply time
424
445
 
446
+ You can also pass in a context at apply time. If you do not pass in a specific
447
+ context at apply time you can still access the context passed in at build time.
448
+ Basically passing in a context at apply time will overwrite the context from before. You can access it simply through
449
+ the node.
450
+
451
+ ```ruby
452
+ build_context = OpenStruct.new(email_validator: ->(node) { node.input.includes?('@') })
453
+ apply_context = OpenStruct.new(default_role: 'BOSS')
454
+
455
+ schema = NxtSchema.schema(:developers, context: build_context) do
456
+ # context at build time
457
+ required(:email, :String).validate(context.email_validator) #
458
+ # access the context at apply time through the node
459
+ required(:role, :String).default { |_, node| node.context.default_role }
460
+ end
461
+
462
+ schema.apply(input: input, context: apply_context)
463
+ ```
464
+
425
465
  ## Development
426
466
 
427
467
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
@@ -438,6 +478,8 @@ The gem is available as open source under the terms of the [MIT License](https:/
438
478
 
439
479
  ## TODO:
440
480
 
481
+ - Explain node interface
482
+ - Add apply! method to readme
441
483
  - Allow to disable validation when applying
442
484
  --> Are there attributes that should be moved to apply time?
443
485
  - Should we have a global and a local registry for validators?
@@ -7,7 +7,7 @@ require 'yaml'
7
7
 
8
8
  require_relative 'nxt_schema/types'
9
9
  require_relative 'nxt_schema/callable'
10
- require_relative 'nxt_schema/application'
10
+ require_relative 'nxt_schema/node'
11
11
  require_relative 'nxt_schema/missing_input'
12
12
  require_relative 'nxt_schema/error'
13
13
  require_relative 'nxt_schema/errors/invalid'
@@ -31,26 +31,26 @@ require_relative 'nxt_schema/validators/excluded_in'
31
31
  require_relative 'nxt_schema/validators/excludes'
32
32
  require_relative 'nxt_schema/validators/query'
33
33
 
34
- require_relative 'nxt_schema/node/on_evaluator'
35
- require_relative 'nxt_schema/node/maybe_evaluator'
36
- require_relative 'nxt_schema/node/type_resolver'
37
- require_relative 'nxt_schema/node/type_system_resolver'
34
+ require_relative 'nxt_schema/template/on_evaluator'
35
+ require_relative 'nxt_schema/template/maybe_evaluator'
36
+ require_relative 'nxt_schema/template/type_resolver'
37
+ require_relative 'nxt_schema/template/type_system_resolver'
38
+ require_relative 'nxt_schema/template/base'
39
+ require_relative 'nxt_schema/template/sub_nodes'
40
+ require_relative 'nxt_schema/template/has_sub_nodes'
41
+ require_relative 'nxt_schema/template/any_of'
42
+ require_relative 'nxt_schema/template/collection'
43
+ require_relative 'nxt_schema/template/schema'
44
+ require_relative 'nxt_schema/template/leaf'
45
+
46
+ require_relative 'nxt_schema/node/errors/schema_error'
47
+ require_relative 'nxt_schema/node/errors/validation_error'
48
+ require_relative 'nxt_schema/node/error_store'
38
49
  require_relative 'nxt_schema/node/base'
39
- require_relative 'nxt_schema/node/sub_nodes'
40
- require_relative 'nxt_schema/node/has_sub_nodes'
41
50
  require_relative 'nxt_schema/node/any_of'
51
+ require_relative 'nxt_schema/node/leaf'
42
52
  require_relative 'nxt_schema/node/collection'
43
53
  require_relative 'nxt_schema/node/schema'
44
- require_relative 'nxt_schema/node/leaf'
45
-
46
- require_relative 'nxt_schema/application/errors/schema_error'
47
- require_relative 'nxt_schema/application/errors/validation_error'
48
- require_relative 'nxt_schema/application/error_store'
49
- require_relative 'nxt_schema/application/base'
50
- require_relative 'nxt_schema/application/any_of'
51
- require_relative 'nxt_schema/application/leaf'
52
- require_relative 'nxt_schema/application/collection'
53
- require_relative 'nxt_schema/application/schema'
54
54
  require_relative 'nxt_schema/dsl'
55
55
 
56
56
  module NxtSchema
@@ -2,8 +2,8 @@ module NxtSchema
2
2
  module Dsl
3
3
  DEFAULT_OPTIONS = { type_system: NxtSchema::Types }.freeze
4
4
 
5
- def collection(name = :root, type: NxtSchema::Node::Collection::DEFAULT_TYPE, **options, &block)
6
- NxtSchema::Node::Collection.new(
5
+ def collection(name = :root, type: NxtSchema::Template::Collection::DEFAULT_TYPE, **options, &block)
6
+ NxtSchema::Template::Collection.new(
7
7
  name: name,
8
8
  type: type,
9
9
  parent_node: nil,
@@ -14,8 +14,8 @@ module NxtSchema
14
14
 
15
15
  alias nodes collection
16
16
 
17
- def schema(name = :roots, type: NxtSchema::Node::Schema::DEFAULT_TYPE, **options, &block)
18
- NxtSchema::Node::Schema.new(
17
+ def schema(name = :roots, type: NxtSchema::Template::Schema::DEFAULT_TYPE, **options, &block)
18
+ NxtSchema::Template::Schema.new(
19
19
  name: name,
20
20
  type: type,
21
21
  parent_node: nil,
@@ -25,7 +25,7 @@ module NxtSchema
25
25
  end
26
26
 
27
27
  def any_of(name = :roots, **options, &block)
28
- NxtSchema::Node::AnyOf.new(
28
+ NxtSchema::Template::AnyOf.new(
29
29
  name: name,
30
30
  parent_node: nil,
31
31
  **DEFAULT_OPTIONS.merge(options),
@@ -35,8 +35,8 @@ module NxtSchema
35
35
 
36
36
  # schema root with NxtSchema::Types::Params type system
37
37
 
38
- def params(name = :params, type: NxtSchema::Node::Schema::DEFAULT_TYPE, **options, &block)
39
- NxtSchema::Node::Schema.new(
38
+ def params(name = :params, type: NxtSchema::Template::Schema::DEFAULT_TYPE, **options, &block)
39
+ NxtSchema::Template::Schema.new(
40
40
  name: name,
41
41
  type: type,
42
42
  parent_node: nil,
@@ -1,5 +1,5 @@
1
1
  module NxtSchema
2
- module Application
2
+ module Node
3
3
 
4
4
  end
5
5
  end
@@ -1,50 +1,38 @@
1
1
  module NxtSchema
2
2
  module Node
3
- class AnyOf < Base
4
- include HasSubNodes
5
-
6
- def initialize(name:, type: nil, parent_node:, **options, &block)
7
- super
8
- end
9
-
10
- def collection(name = sub_nodes.count, type = NxtSchema::Node::Collection::DEFAULT_TYPE, **options, &block)
11
- super
3
+ class AnyOf < Node::Base
4
+ def valid?
5
+ valid_application.present?
12
6
  end
13
7
 
14
- def schema(name = sub_nodes.count, type = NxtSchema::Node::Schema::DEFAULT_TYPE, **options, &block)
15
- super
16
- end
17
-
18
- def node(name = sub_nodes.count, node_or_type_of_node = nil, **options, &block)
19
- super
20
- end
8
+ def call
9
+ child_applications.map(&:call)
21
10
 
22
- # TODO: Maybe overwrite sub node methods to not have to provide a name here and use node count instead
11
+ if valid?
12
+ self.output = valid_application.output
13
+ else
14
+ child_applications.each do |application|
15
+ merge_errors(application)
16
+ end
17
+ end
23
18
 
24
- def on(*args)
25
- raise NotImplementedError
26
- end
27
-
28
- def maybe(*args)
29
- raise NotImplementedError
19
+ self
30
20
  end
31
21
 
32
22
  private
33
23
 
34
- def resolve_type(name_or_type)
35
- nil
36
- end
24
+ delegate :[], to: :child_applications
37
25
 
38
- def resolve_optional_option
39
- return unless options.key?(:optional)
40
-
41
- raise InvalidOptions, "The optional option is not available for nodes of type #{self.class.name}"
26
+ def valid_application
27
+ child_applications.find(&:valid?)
42
28
  end
43
29
 
44
- def resolve_omnipresent_option
45
- return unless options.key?(:omnipresent)
30
+ def child_applications
31
+ @child_applications ||= nodes.map { |node| node.build_application(input: input, context: context, parent: self) }
32
+ end
46
33
 
47
- raise InvalidOptions, "The omnipresent option is not available for nodes of type #{self.class.name}"
34
+ def nodes
35
+ @nodes ||= node.sub_nodes.values
48
36
  end
49
37
  end
50
38
  end