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 +4 -4
- data/README.md +129 -48
- data/config/locales/en.yml +1 -0
- data/datacaster.gemspec +1 -1
- data/lib/datacaster/base.rb +1 -1
- data/lib/datacaster/comparator.rb +1 -1
- data/lib/datacaster/context_nodes/pass_if.rb +11 -0
- data/lib/datacaster/definition_dsl.rb +0 -1
- data/lib/datacaster/predefined.rb +55 -7
- data/lib/datacaster/result.rb +5 -5
- data/lib/datacaster/switch_node.rb +72 -0
- data/lib/datacaster/then_node.rb +1 -1
- data/lib/datacaster/version.rb +1 -1
- metadata +5 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d563422d81d874ec9f74b4e95a3d24a7a59cf2c3f50ad709a226346616364882
|
4
|
+
data.tar.gz: e33c3dffdbff37e5ce711f56c0196e8955379764c98c2429ec391fd33819797d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
19
|
-
- [*OR operator
|
20
|
-
- [*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
|
303
|
+
Let's support we want to run different validations depending on some value, e.g.:
|
302
304
|
|
303
|
-
|
304
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
411
|
+
Notice that shortcut definitions are available (illustrated in the example above) for the switch caster:
|
347
412
|
|
348
|
-
|
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
|
-
|
417
|
+
`switch()` without a `base` argument will pass the incoming value to the `.on(...)` casters.
|
351
418
|
|
352
|
-
|
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
|
-
`
|
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
|
-
|
427
|
+
I18n keys:
|
358
428
|
|
359
|
-
|
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
|
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
|
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
|
-
|
576
|
-
|
577
|
-
|
578
|
-
|
579
|
-
|
580
|
-
|
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
|
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)
|
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')`
|
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!`
|
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
|
|
data/config/locales/en.yml
CHANGED
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
|
data/lib/datacaster/base.rb
CHANGED
@@ -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
|
-
|
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
|
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(
|
260
|
+
Datacaster.ErrorResult(I18nValues::Key.new(error_keys, value: x))
|
213
261
|
end
|
214
262
|
end
|
215
263
|
end
|
data/lib/datacaster/result.rb
CHANGED
@@ -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
|
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
|
data/lib/datacaster/then_node.rb
CHANGED
@@ -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:)
|
data/lib/datacaster/version.rb
CHANGED
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.
|
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-
|
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: :
|
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
|