datacaster 3.0.0 → 3.1.1

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: 3b77dec970a054162cb38f106f29f74a505d5c0e3084d46b8c578c2657b63078
4
- data.tar.gz: ea0b92d48730865ed975c5354e304a6f2baaafd3672af83796763ea968aafe73
3
+ metadata.gz: d563422d81d874ec9f74b4e95a3d24a7a59cf2c3f50ad709a226346616364882
4
+ data.tar.gz: e33c3dffdbff37e5ce711f56c0196e8955379764c98c2429ec391fd33819797d
5
5
  SHA512:
6
- metadata.gz: 196c625a37388f5debe9ae393f2abcddbedef154e874ce37bc7b5c392d19cfe67921f5aca62321c38fb4d7e7ca8cc825f903e69ebc9e36c62780c9f545116b15
7
- data.tar.gz: f2e4dbe9fa520f51080ad6140fefed7ca87dab429f7af9d720582ee6378be5279607340d444ccdd8125ea0b8f23eff9da7b2c0e5c0055faff92bb10b3235887c
6
+ metadata.gz: d01eb486e4d11954f672e6958803b7ea4c60430c58850f2b4c8e8b16a500229c28a91ecadc45317775f7ff8098c4beddfb0373d415f72e5c917ccb1a93f589c8
7
+ data.tar.gz: c9bdb6f156a7de6eb81fa9cb87dcbf050fd47292d417afd4d085cb64a9e5460c4022b6471de723a1e112cd730a5b12e14c595e9ce248e30ff18fd84625a2cc8a
data/README.md CHANGED
@@ -15,9 +15,10 @@ It is currently used in production in several projects (mainly as request parame
15
15
  - [Result value](#result-value)
16
16
  - [Hash schema](#hash-schema)
17
17
  - [Logical operators](#logical-operators)
18
- - [*AND operator*:](#and-operator)
19
- - [*OR operator*:](#or-operator)
20
- - [*IF... THEN... ELSE operator*:](#if-then-else-operator)
18
+ - [*AND operator*](#and-operator)
19
+ - [*OR operator*](#or-operator)
20
+ - [*IF... THEN... ELSE operator*](#if-then-else-operator)
21
+ - [*SWITCH... ON... ELSE operator*](#switch-on-else-operator)
21
22
  - [Built-in types](#built-in-types)
22
23
  - [Basic types](#basic-types)
23
24
  - [`array(error_key = nil)`](#arrayerror_key--nil)
@@ -31,13 +32,14 @@ It is currently used in production in several projects (mainly as request parame
31
32
  - [`integer32(error_key = nil)`](#integer32error_key--nil)
32
33
  - [`non_empty_string(error_key = nil)`](#non_empty_stringerror_key--nil)
33
34
  - [Special types](#special-types)
34
- - [`absent(error_key = nil)`](#absenterror_key--nil)
35
+ - [`absent(error_key = nil, on: nil)`](#absenterror_key--nil-on-nil)
35
36
  - [`any(error_key = nil)`](#anyerror_key--nil)
36
37
  - [`default(default_value, on: nil)`](#defaultdefault_value-on-nil)
37
38
  - [`merge_message_keys(*keys)`](#merge_message_keyskeys)
38
39
  - [`must_be(klass, error_key = nil)`](#must_beklass-error_key--nil)
39
- - [`optional(base)`](#optionalbase)
40
+ - [`optional(base, on: nil)`](#optionalbase-on-nil)
40
41
  - [`pass`](#pass)
42
+ - [`pass_if(base)`](#pass_ifbase)
41
43
  - [`pick(key)`](#pickkey)
42
44
  - [`remove`](#remove)
43
45
  - [`responds_to(method, error_key = nil)`](#responds_tomethod-error_key--nil)
@@ -266,7 +268,7 @@ And one special: AND with error aggregation (`*`).
266
268
 
267
269
  The former 3 is described immediately below, and the latter is described in the section on hash schemas further in this file.
268
270
 
269
- #### *AND operator*:
271
+ #### *AND operator*
270
272
 
271
273
  ```ruby
272
274
  even_number = Datacaster.schema { integer & check { |x| x.even? } }
@@ -282,7 +284,7 @@ even_number.("test")
282
284
 
283
285
  If left-hand validation of AND operator passes, *its result* (not the original value) is passed to the right-hand validation. See below in this file section on transformations where this might be relevant.
284
286
 
285
- #### *OR operator*:
287
+ #### *OR operator*
286
288
 
287
289
  ```ruby
288
290
  # 'compare' custom type returns ValidResult if and only if validated value == compare's argument
@@ -296,12 +298,57 @@ person_or_entity.(:ngo) # => Datacaster::ErrorResult(["does not equal :entity
296
298
 
297
299
  Notice that OR operator, if left-hand validation fails, passes the original value to the right-hand validation. As you see in the example above resultant error messages are not always convenient (i.e. to show something like "value must be :person or :entity" is preferable to showing somewhat misleading "must be equal to :entity"). See the next section on "IF... THEN... ELSE" for closer to the real world example.
298
300
 
299
- #### *IF... THEN... ELSE operator*:
301
+ #### *IF... THEN... ELSE operator*
300
302
 
301
- Let's suppose we want to validate that incoming hash is either 'person' or 'entity', where
303
+ Let's support we want to run different validations depending on some value, e.g.:
302
304
 
303
- - 'person' is a hash with 3 keys (kind: `:person`, name: string, salary: integer),
304
- - 'entity' is a hash with 4 keys (kind: `:entity`, title: string, form: string, revenue: integer).
305
+ * if 'salary' is more than 100_000, check for the additional key, 'passport'
306
+ * otherwise, ensure 'passport' key is absent
307
+ * in any case, check that 'name' key is present and is a string
308
+
309
+ ```ruby
310
+ applicant =
311
+ Datacaster.schema do
312
+ base = hash_schema(
313
+ name: string,
314
+ salary: integer
315
+ )
316
+
317
+ large_salary = check { |x| x[:salary] > 100_000 }
318
+
319
+ base &
320
+ large_salary.
321
+ then(passport: string).
322
+ else(passport: absent)
323
+ end
324
+
325
+ applicant.(name: 'John', salary: 50_000)
326
+ # => Datacaster::ValidResult({:name=>"John", :salary=>50000})
327
+
328
+ applicant.(name: 'Jane', salary: 101_000, passport: 'AB123CD')
329
+ # => Datacaster::ValidResult({:name=>"Jane", :salary=>101000, :passport=>"AB123CD"})
330
+
331
+ applicant.(name: 'George', salary: 101_000)
332
+ # => Datacaster::ErrorResult({:passport=>["is not a string"])
333
+ ```
334
+
335
+ Formally, with `a.then(b).else(c)`:
336
+
337
+ * if `a` returns `ValidResult`, then `b` is called *with the result of `a`* (not the original value) and whatever `b` returns is returned;
338
+ * otherwise, `c` is called with the original value, and whatever `c` returns is returned.
339
+
340
+ `else`-part is required and could not be omitted.
341
+
342
+ Note: this construct is *not* an equivalent of `a & b | c`.
343
+
344
+ With `a.then(b).else(c)` if `a` and `b` fails, then `b`'s error is returned. With `a & b | c`, instead, `c`'s result would be returned.
345
+
346
+ #### *SWITCH... ON... ELSE operator*
347
+
348
+ Let's suppose we want to validate that incoming hash is either 'person' or 'entity', where:
349
+
350
+ * 'person' is a hash with 3 keys (kind: `:person`, name: string, salary: integer),
351
+ * 'entity' is a hash with 4 keys (kind: `:entity`, title: string, form: string, revenue: integer).
305
352
 
306
353
  ```ruby
307
354
  person_or_entity =
@@ -317,7 +364,25 @@ person_or_entity =
317
364
  # separate entity validator (excluding validation of 'kind' field)
318
365
  entity = hash_schema(title: string, form: string, revenue: integer)
319
366
 
320
- kind_is_valid & hash_schema(kind: compare(:person)).then(person).else(entity)
367
+
368
+ # 1. First option, explicit definition
369
+
370
+ kind_is_valid &
371
+ switch(pick(:kind)).
372
+ on(compare(:person), person).
373
+ on(compare(:entity), entity)
374
+
375
+ # 2. Second option, shortcut definiton
376
+
377
+ kind_is_valid &
378
+ switch(:kind).
379
+ on(:person, person).
380
+ on(:entity, entity)
381
+
382
+ # 3. Third option, using keywords args and Ruby 3.1 value omission in hash literals
383
+
384
+ kind_is_valid &
385
+ switch(:kind, person:, entity:)
321
386
  end
322
387
 
323
388
  person_or_entity.(
@@ -341,22 +406,27 @@ person_or_entity.(
341
406
  # => Datacaster::ErrorResult({:kind=>["is invalid"]})
342
407
  ```
343
408
 
344
- See below documentation on 'check' custom type to know how to provide custom error message instead of 'is invalid'.
409
+ In our opinion the above example shows most laconic way to express underlying 'business-logic' (including elaborate error reporting on all kinds of failures) among all available competitor approaches/gems.
345
410
 
346
- Schema, defined above, behaves in all aspects (shown in the example and in other practical applications which might come to your mind) just as you might expect it to, after reading previous examples and the code above.
411
+ Notice that shortcut definitions are available (illustrated in the example above) for the switch caster:
347
412
 
348
- In our opinion the above example shows most laconic way to express underlying 'business-logic' (including elaborate error reporting on all kinds of failures) among all available competitor approaches/gems.
413
+ * `switch(:key)` is exactly the same as `switch(pick(:key))` (works for a string, a symbol, or an array thereof)
414
+ * `on(:key, ...)` is exactly the same as `on(compare(:key), ...)` (works for a string or a symbol)
415
+ * `switch([caster], on_check => on_caster, ...)` is exactly the same as `switch([caster]).on(on_check, on_caster).on(...)`
349
416
 
350
- Formally, with `a.then(b).else(c)`:
417
+ `switch()` without a `base` argument will pass the incoming value to the `.on(...)` casters.
351
418
 
352
- * if `a` returns `ValidResult`, then `b` is called *with the result of `a`* (not the original value) and whatever `b` returns is returned;
353
- * otherwise, `c` is called with the original value, and whatever `c` returns is returned.
419
+ Formally, with `switch(a).on(on_check, on_caster).else(c)`:
354
420
 
355
- `else`-part is required and could not be omitted.
421
+ * if `a` returns ErrorResult, it is the result of the switch
422
+ * otherwise, all `on_check` casters from the `.on` blocks are called with the result of `a`, until the first one which returns ValidResult is found – corresponding `on_caster` is called with the original value and its result is the result of the switch
423
+ * if all `on_check`-s returned ErrorResult
424
+ * and there is an `.else` block, `c` is called with the original value and its result is the result of the switch
425
+ * if there is no `.else` block, `ErrorResult(['is invalid'])` is returned from the switch
356
426
 
357
- Note: this construct is *not* an equivalent of `a & b | c`.
427
+ I18n keys:
358
428
 
359
- With `a.then(b).else(c)` if `a` and `b` fails, then `b`'s error is returned. With `a & b | c`, instead, `c`'s result would be returned.
429
+ * all `.on` checks resulted in an error and there is no `.else`: `'.switch'`, `'datacaster.errors.switch'`
360
430
 
361
431
  ## Built-in types
362
432
 
@@ -432,9 +502,16 @@ Returns ValidResult if and only if provided value is a string and is not empty.
432
502
 
433
503
  ### Special types
434
504
 
435
- #### `absent(error_key = nil)`
505
+ #### `absent(error_key = nil, on: nil)`
436
506
 
437
- Returns ValidResult if and only if provided value is `Datacaster.absent` (this is singleton instance). Relevant only for hash schemas (see below). Doesn't transform the value.
507
+ Returns ValidResult if and only if provided value is absent. Relevant only for hash schemas (see below). Transforms the value to `Datacaster.absent`.
508
+
509
+ The value is considered absent:
510
+
511
+ * if the value is `Datacaster.absent` (`on` is disregarded in such case)
512
+ * if `on` is set to a method name to which the value responds and yields truthy
513
+
514
+ Set `on` to `:nil?`, `:empty?` or similar method names.
438
515
 
439
516
  I18n keys: `error_key`, `'.absent'`, `'datacaster.errors.absent'`.
440
517
 
@@ -450,10 +527,10 @@ Always returns ValidResult.
450
527
 
451
528
  Returns `default_value` in the following cases:
452
529
 
453
- * if value is `Datacaster.absent` (`on` is disregarded in such case)
454
- * if `on` is set to method name to which the value responds and yields truthy
530
+ * if the value is `Datacaster.absent` (`on` is disregarded in such case)
531
+ * if `on` is set to a method name to which the value responds and yields truthy
455
532
 
456
- Returns initial value otherwise.
533
+ Returns the initial value otherwise.
457
534
 
458
535
  Set `on` to `:nil?`, `:empty?` or similar method names.
459
536
 
@@ -566,18 +643,25 @@ Returns ValidResult if and only if the value `#is_a?(klass)`. Doesn't transform
566
643
 
567
644
  I18n keys: `error_key`, `'.must_be'`, `'datacaster.errors.must_be'`. Adds `reference` i18n variable, setting it to `klass.name`.
568
645
 
569
- #### `optional(base)`
646
+ #### `optional(base, on: nil)`
570
647
 
571
- Returns ValidResult if and only if the value is either `Datacaster.absent` or passes `base` validation. See below documentation on hash schemas for details on `Datacaster.absent`.
648
+ Returns ValidResult if and only if the value is either absent or passes `base` validation. In the value is absent, transforms it to the `Datacaster.absent`. Otherwise, returns `base` result.
649
+
650
+ Value is considered absent:
651
+
652
+ * if the value is `Datacaster.absent` (`on` is disregarded in such case)
653
+ * if `on` is set to a method name to which the value responds and yields truthy
654
+
655
+ Set `on` to `:nil?`, `:empty?` or similar method names.
572
656
 
573
657
  ```ruby
574
658
  item_with_optional_price =
575
- Datacaster.schema do
576
- hash_schema(
577
- name: string,
578
- price: optional(float)
579
- )
580
- end
659
+ Datacaster.schema do
660
+ hash_schema(
661
+ name: string,
662
+ price: optional(float)
663
+ )
664
+ end
581
665
 
582
666
  item_with_optional_price.(name: "Book", price: 1.23)
583
667
  # => Datacaster::ValidResult({:name=>"Book", :price=>1.23})
@@ -594,6 +678,12 @@ Always returns ValidResult. Doesn't transform the value.
594
678
 
595
679
  Useful to "mark" the value as validated (see section below on hash schemas, where this could be applied).
596
680
 
681
+ #### `pass_if(base)`
682
+
683
+ Returns ValidResult if and only if base returns ValidResult. Returns base's error result otherwise.
684
+
685
+ Doesn't transform the value: if base succeeds returns the original value (not the one that base returned).
686
+
597
687
  #### `pick(key)`
598
688
 
599
689
  Returns ValidResult if and only if the value `#is_a?(Enumerable)`.
@@ -642,15 +732,7 @@ I18n keys: `error_key`, `'.responds_to'`, `'datacaster.errors.responds_to'`. Add
642
732
 
643
733
  #### `transform_to_value(value)`
644
734
 
645
- Always returns ValidResult. The value is transformed to provided argument. Is used to provide default values, e.g.:
646
-
647
- ```ruby
648
- max_concurrent_connections = Datacaster.schema { compare(nil).then(transform_to_value(5)).else(integer) }
649
-
650
- max_concurrent_connections.(9) # => Datacaster::ValidResult(9)
651
- max_concurrent_connections.("9") # => Datacaster::ErrorResult(["is not an integer"])
652
- max_concurrent_connections.(nil) # => Datacaster::ValidResult(5)
653
- ```
735
+ Always returns ValidResult. The value is transformed to provided argument (disregarding the original value). See also [`default`](#defaultdefault_value-on-nil).
654
736
 
655
737
  ### "Web-form" types
656
738
 
@@ -957,7 +1039,7 @@ restricted_params.(username: "test", is_admin: nil)
957
1039
 
958
1040
  More practical case is to include `absent` validator in logical expressions, e.g. `something: absent | string`. If `something` is set to `nil`, this validation will fail, which could be the desired (and hardly achieved by any other validation framework) behavior.
959
1041
 
960
- Also, see documentation for [`optional(base)`](#optionalbase) and [`optional_param(base)`](#optional_parambase). If some value becomes `Datacaster.absent` in its chain of validations-transformations, it is removed from the resultant hash (on the same stage where the lack of extra/unchecked keys in the hash is validated):
1042
+ Also, see documentation for [`optional(base)`](#optionalbase-on-nil) and [`optional_param(base)`](#optional_parambase). If some value becomes `Datacaster.absent` in its chain of validations-transformations, it is removed from the resultant hash (on the same stage where the lack of extra/unchecked keys in the hash is validated):
961
1043
 
962
1044
  ```ruby
963
1045
  person =
@@ -1375,7 +1457,7 @@ en:
1375
1457
  wrong_format: wrong format
1376
1458
  ```
1377
1459
 
1378
- Let's gradually reduce the boilerplate, starting with the most explicit example. Notice that all relative keys (i.e. keys which will be scoped during the execution) starts with `'.'`:
1460
+ Let's gradually reduce the boilerplate, starting with the most explicit example. Notice that all relative keys (i.e. keys which will be scoped during the execution) start with `'.'`:
1379
1461
 
1380
1462
  ```ruby
1381
1463
  schema =
@@ -1436,7 +1518,6 @@ schema =
1436
1518
  Datacaster.schema(i18n_scope: 'user') do
1437
1519
  check { |v| v[:id] == 1 } &
1438
1520
  hash_schema(
1439
- # '.wrong_format' inferred to be '.name.wrong_format'
1440
1521
  name: check { false }
1441
1522
  )
1442
1523
  end
@@ -1470,9 +1551,9 @@ Every caster will automatically provide `value` variable for i18n interpolation.
1470
1551
 
1471
1552
  All keyword arguments of `#i18n_key`, `#i18n_scope` and designed for that sole purpose `#i18n_vars` are provided as interpolation variables on i18n.
1472
1553
 
1473
- It is possible to add i18n variables at the runtime (e.g. inside `check { ... }` block) by calling `i18n_vars!(variable: 'value')` (or `i18n_var!(:variable, 'value')`.
1554
+ It is possible to add i18n variables at the runtime (e.g. inside `check { ... }` block) by calling `i18n_vars!(variable: 'value')` or `i18n_var!(:variable, 'value')`.
1474
1555
 
1475
- Outer calls of `#i18n_key` (`#i18n_scope`, `#i18n_vars`) have presedence before the inner if variable names collide. However, runtime calls of `#i18n_vars!` and `#i18n_var!` overwrites compile-time variables from the next nearest key, scope or vars on collision.
1556
+ Outer calls of `#i18n_key` (`#i18n_scope`, `#i18n_vars`) have presedence before the inner if variable names collide. However, runtime calls of `#i18n_vars!` and `#i18n_var!` overwrite compile-time variables from the next nearest key, scope or vars on collision.
1476
1557
 
1477
1558
  ## Registering custom 'predefined' types
1478
1559
 
@@ -21,4 +21,5 @@ en:
21
21
  to_float: does not look like a float
22
22
  to_integer: does not look like an integer
23
23
  non_empty_string: should be non-empty string
24
+ switch: is invalid
24
25
  try: raised an error
data/datacaster.gemspec CHANGED
@@ -26,7 +26,7 @@ Gem::Specification.new do |spec|
26
26
  spec.add_development_dependency 'rake', '~> 12.0'
27
27
  spec.add_development_dependency 'rspec', '~> 3.0'
28
28
  spec.add_development_dependency 'i18n', '~> 1.14'
29
+ spec.add_development_dependency 'dry-monads', '>= 1.3', '< 1.4'
29
30
 
30
- spec.add_runtime_dependency 'dry-monads', '>= 1.3', '< 1.4'
31
31
  spec.add_runtime_dependency 'zeitwerk', '>= 2', '< 3'
32
32
  end
@@ -46,7 +46,7 @@ module Datacaster
46
46
  end
47
47
 
48
48
  def then(other)
49
- ThenNode.new(self, other)
49
+ ThenNode.new(self, DefinitionDSL.expand(other))
50
50
  end
51
51
 
52
52
  def with_context(context)
@@ -18,7 +18,7 @@ module Datacaster
18
18
  end
19
19
 
20
20
  def inspect
21
- "#<Datacaster::Comparator>"
21
+ "#<Datacaster::Comparator(#{@value.inspect})>"
22
22
  end
23
23
  end
24
24
  end
@@ -0,0 +1,11 @@
1
+ module Datacaster
2
+ module ContextNodes
3
+ class PassIf < Datacaster::ContextNode
4
+ def cast(object, runtime:)
5
+ @runtime = create_runtime(runtime)
6
+ result = @base.with_runtime(@runtime).call(object)
7
+ result.valid? ? Datacaster::ValidResult(object) : result
8
+ end
9
+ end
10
+ end
11
+ end
@@ -4,7 +4,6 @@ require 'date'
4
4
  module Datacaster
5
5
  class DefinitionDSL
6
6
  include Datacaster::Predefined
7
- include Dry::Monads[:result]
8
7
 
9
8
  # Translates hashes like {a: <IntegerChecker>} to <HashSchema {a: <IntegerChecker>}>
10
9
  # and arrays like [<IntegerChecker>] to <ArraySchema <IntegerChecker>>
@@ -53,12 +53,34 @@ module Datacaster
53
53
  Validator.new(active_model_validations)
54
54
  end
55
55
 
56
+ def schema(base)
57
+ ContextNodes::StructureCleaner.new(base, strategy: :fail)
58
+ end
59
+
60
+ def choosy_schema(base)
61
+ ContextNodes::StructureCleaner.new(base, strategy: :remove)
62
+ end
63
+
64
+ def partial_schema(base)
65
+ ContextNodes::StructureCleaner.new(base, strategy: :pass)
66
+ end
67
+
56
68
  # 'Meta' types
57
69
 
58
- def absent(error_key = nil)
70
+ def absent(error_key = nil, on: nil)
59
71
  error_keys = ['.absent', 'datacaster.errors.absent']
60
72
  error_keys.unshift(error_key) if error_key
61
- check { |x| x == Datacaster.absent }.i18n_key(*error_keys)
73
+
74
+ cast do |x|
75
+ if x == Datacaster.absent ||
76
+ (!on.nil? && x.respond_to?(on) && x.public_send(on))
77
+ Datacaster.ValidResult(Datacaster.absent)
78
+ else
79
+ Datacaster.ErrorResult(
80
+ I18nValues::Key.new(error_keys, value: x)
81
+ )
82
+ end
83
+ end
62
84
  end
63
85
 
64
86
  def any(error_key = nil)
@@ -70,7 +92,7 @@ module Datacaster
70
92
  def default(value, on: nil)
71
93
  transform do |x|
72
94
  if x == Datacaster.absent ||
73
- (on && x.respond_to?(on) && x.public_send(on))
95
+ (!on.nil? && x.respond_to?(on) && x.public_send(on))
74
96
  value
75
97
  else
76
98
  x
@@ -90,7 +112,25 @@ module Datacaster
90
112
  transform(&:itself)
91
113
  end
92
114
 
93
- def pick(*keys)
115
+ def switch(*base, **on_clauses)
116
+ switch =
117
+ if base.length == 0
118
+ SwitchNode.new
119
+ else
120
+ SwitchNode.new(base)
121
+ end
122
+ on_clauses.reduce(switch) do |result, (k, v)|
123
+ result.on(k, v)
124
+ end
125
+ end
126
+
127
+ def pass_if(base)
128
+ ContextNodes::PassIf.new(base)
129
+ end
130
+
131
+ def pick(*keys, strict: false)
132
+ raise RuntimeError.new("provide keys to pick, e.g. pick(:key)") if keys.empty?
133
+
94
134
  must_be(Enumerable) & transform { |value|
95
135
  result =
96
136
  keys.map do |key|
@@ -123,8 +163,16 @@ module Datacaster
123
163
  check { |x| x.is_a?(klass) }.i18n_key(*error_keys, reference: klass.name)
124
164
  end
125
165
 
126
- def optional(base)
127
- absent | base
166
+ def optional(base, on: nil)
167
+ return absent | base if on == nil
168
+ cast do |x|
169
+ if x == Datacaster.absent ||
170
+ (!on.nil? && x.respond_to?(on) && x.public_send(on))
171
+ Datacaster.ValidResult(Datacaster.absent)
172
+ else
173
+ base.(x)
174
+ end
175
+ end
128
176
  end
129
177
 
130
178
  # Strict types
@@ -209,7 +257,7 @@ module Datacaster
209
257
  elsif ['false', '0', false].include?(x)
210
258
  Datacaster.ValidResult(false)
211
259
  else
212
- Datacaster.ErrorResult(Datacaster::I18nValues::Key.new(error_keys, value: x))
260
+ Datacaster.ErrorResult(I18nValues::Key.new(error_keys, value: x))
213
261
  end
214
262
  end
215
263
  end
@@ -1,9 +1,5 @@
1
- require 'dry/monads'
2
-
3
1
  module Datacaster
4
2
  class Result
5
- include Dry::Monads[:result]
6
-
7
3
  def initialize(valid, value_or_errors)
8
4
  @value_or_errors = value_or_errors
9
5
  if !valid && !@value_or_errors.is_a?(Hash) && !@value_or_errors.is_a?(Array)
@@ -43,7 +39,11 @@ module Datacaster
43
39
  end
44
40
 
45
41
  def to_dry_result
46
- @valid ? Success(@value_or_errors) : Failure(errors)
42
+ if @valid
43
+ Dry::Monads::Result::Success.new(@value_or_errors)
44
+ else
45
+ Dry::Monads::Result::Failure.new(errors)
46
+ end
47
47
  end
48
48
 
49
49
  private
@@ -0,0 +1,72 @@
1
+ module Datacaster
2
+ class SwitchNode < Base
3
+ def initialize(base = nil, on_casters = [], else_caster = nil)
4
+ base = base[0] if base.is_a?(Array) && base.length == 1
5
+
6
+ case base
7
+ when nil
8
+ @base = nil
9
+ when Datacaster::Base
10
+ @base = base
11
+ when String, Symbol, Array
12
+ @base = Datacaster::Predefined.pick(base)
13
+ else
14
+ raise RuntimeError, "provide a Datacaster::Base instance, a hash key, or an array of keys to switch(...) caster", caller
15
+ end
16
+
17
+ @ons = on_casters
18
+ @else = else_caster
19
+ end
20
+
21
+ def on(caster_or_value, clause)
22
+ caster =
23
+ case caster_or_value
24
+ when Datacaster::Base
25
+ caster_or_value
26
+ else
27
+ Datacaster::Predefined.compare(caster_or_value)
28
+ end
29
+
30
+ clause = DefinitionDSL.expand(clause)
31
+
32
+ self.class.new(@base, @ons + [[caster, clause]], @else)
33
+ end
34
+
35
+ def else(else_caster)
36
+ raise ArgumentError, "Datacaster: double else clause is not permitted", caller if @else
37
+ self.class.new(@base, @ons, else_caster)
38
+ end
39
+
40
+ def cast(object, runtime:)
41
+ if @ons.empty?
42
+ raise RuntimeError, "switch caster requires at least one 'on' statement: switch(...).on(condition, cast)", caller
43
+ end
44
+
45
+ if @base.nil?
46
+ switch_result = object
47
+ else
48
+ switch_result = @base.with_runtime(runtime).(object)
49
+ return switch_result unless switch_result.valid?
50
+ switch_result = switch_result.value
51
+ end
52
+
53
+ @ons.each do |check, clause|
54
+ result = check.with_runtime(runtime).(switch_result)
55
+ next unless result.valid?
56
+
57
+ return clause.with_runtime(runtime).(object)
58
+ end
59
+
60
+ # all 'on'-s have failed
61
+ return @else.with_runtime(runtime).(object) if @else
62
+
63
+ Datacaster.ErrorResult(
64
+ I18nValues::Key.new(['.switch', 'datacaster.errors.switch'], value: object)
65
+ )
66
+ end
67
+
68
+ def inspect
69
+ "#<Datacaster::SwitchNode base: #{@base.inspect} on: #{@ons.inspect} else: #{@else.inspect}>"
70
+ end
71
+ end
72
+ end
@@ -9,7 +9,7 @@ module Datacaster
9
9
  def else(else_caster)
10
10
  raise ArgumentError.new("Datacaster: double else clause is not permitted") if @else
11
11
 
12
- self.class.new(@left, @then, else_caster)
12
+ self.class.new(@left, @then, DefinitionDSL.expand(else_caster))
13
13
  end
14
14
 
15
15
  def cast(object, runtime:)
@@ -1,3 +1,3 @@
1
1
  module Datacaster
2
- VERSION = "3.0.0"
2
+ VERSION = "3.1.1"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: datacaster
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.0.0
4
+ version: 3.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Eugene Zolotarev
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-09-29 00:00:00.000000000 Z
11
+ date: 2023-10-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activemodel
@@ -76,7 +76,7 @@ dependencies:
76
76
  - - "<"
77
77
  - !ruby/object:Gem::Version
78
78
  version: '1.4'
79
- type: :runtime
79
+ type: :development
80
80
  prerelease: false
81
81
  version_requirements: !ruby/object:Gem::Requirement
82
82
  requirements:
@@ -140,6 +140,7 @@ files:
140
140
  - lib/datacaster/context_nodes/errors_caster.rb
141
141
  - lib/datacaster/context_nodes/i18n.rb
142
142
  - lib/datacaster/context_nodes/i18n_keys_mapper.rb
143
+ - lib/datacaster/context_nodes/pass_if.rb
143
144
  - lib/datacaster/context_nodes/structure_cleaner.rb
144
145
  - lib/datacaster/context_nodes/user_context.rb
145
146
  - lib/datacaster/definition_dsl.rb
@@ -157,6 +158,7 @@ files:
157
158
  - lib/datacaster/runtimes/structure_cleaner.rb
158
159
  - lib/datacaster/runtimes/user_context.rb
159
160
  - lib/datacaster/substitute_i18n.rb
161
+ - lib/datacaster/switch_node.rb
160
162
  - lib/datacaster/then_node.rb
161
163
  - lib/datacaster/transformer.rb
162
164
  - lib/datacaster/trier.rb