datacaster 3.1.1 → 3.1.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +90 -15
- data/config/locales/en.yml +2 -0
- data/lib/datacaster/and_with_error_aggregation_node.rb +1 -1
- data/lib/datacaster/base.rb +1 -93
- data/lib/datacaster/caster.rb +4 -4
- data/lib/datacaster/hash_mapper.rb +2 -2
- data/lib/datacaster/mixin.rb +68 -0
- data/lib/datacaster/predefined.rb +126 -42
- data/lib/datacaster/switch_node.rb +7 -9
- data/lib/datacaster/utils.rb +40 -0
- data/lib/datacaster/version.rb +1 -1
- data/lib/datacaster.rb +5 -1
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d7cd7191206c373ab38c5516505bf38cbbdc76a7b7c433f976498accda34b054
|
4
|
+
data.tar.gz: 0443edf8c8f494caeacfb55ba902cce19210a53cf9f726e8306f7e487de58fd8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2f00d6cd8c2aae72cba3723dbea6b1c6cb9d760309e9fefa7897e5da691123459496348045849f47700f241df668e505120ba1d2e2f54c93a033e72b222dc3b9
|
7
|
+
data.tar.gz: 15c5d76c7cc7a16be05890bec3bcf549c0a5d103e9ba8bb42e2743c834e874bc1296fa5daa3af4feff93107d5acd8db2bd3b2da103f1c29aca1a169b86883c24
|
data/README.md
CHANGED
@@ -34,13 +34,14 @@ It is currently used in production in several projects (mainly as request parame
|
|
34
34
|
- [Special types](#special-types)
|
35
35
|
- [`absent(error_key = nil, on: nil)`](#absenterror_key--nil-on-nil)
|
36
36
|
- [`any(error_key = nil)`](#anyerror_key--nil)
|
37
|
+
- [`attribute(*keys)`](#attributekeys)
|
37
38
|
- [`default(default_value, on: nil)`](#defaultdefault_value-on-nil)
|
38
39
|
- [`merge_message_keys(*keys)`](#merge_message_keyskeys)
|
39
40
|
- [`must_be(klass, error_key = nil)`](#must_beklass-error_key--nil)
|
40
41
|
- [`optional(base, on: nil)`](#optionalbase-on-nil)
|
41
42
|
- [`pass`](#pass)
|
42
43
|
- [`pass_if(base)`](#pass_ifbase)
|
43
|
-
- [`pick(
|
44
|
+
- [`pick(*keys)`](#pickkeys)
|
44
45
|
- [`remove`](#remove)
|
45
46
|
- [`responds_to(method, error_key = nil)`](#responds_tomethod-error_key--nil)
|
46
47
|
- [`transform_to_value(value)`](#transform_to_valuevalue)
|
@@ -56,6 +57,8 @@ It is currently used in production in several projects (mainly as request parame
|
|
56
57
|
- [`try(error_key = nil, catched_exception:) { |value| ... }`](#tryerror_key--nil-catched_exception--value--)
|
57
58
|
- [`validate(active_model_validations, name = 'Anonymous')`](#validateactive_model_validations-name--anonymous)
|
58
59
|
- [`compare(reference_value, error_key = nil)`](#comparereference_value-error_key--nil)
|
60
|
+
- [`included_in(*reference_values, error_key: nil)`](#included_inreference_values-error_key-nil)
|
61
|
+
- [`relate(left, op, right, error_key: nil)`](#relateleft-op-right-error_key-nil)
|
59
62
|
- [`transform { |value| ... }`](#transform--value--)
|
60
63
|
- [`transform_if_present { |value| ... }`](#transform_if_present--value--)
|
61
64
|
- [Array schemas](#array-schemas)
|
@@ -175,10 +178,10 @@ array = Datacaster.schema { array }
|
|
175
178
|
array.(nil)
|
176
179
|
|
177
180
|
# In this README
|
178
|
-
|
181
|
+
# => Datacaster::ErrorResult(['should be an array'])
|
179
182
|
|
180
183
|
# In reality
|
181
|
-
|
184
|
+
# => <Datacaster::ErrorResult([#<Datacaster::I18nValues::Key(.array, datacaster.errors.array) {:value=>nil}>])>
|
182
185
|
```
|
183
186
|
|
184
187
|
See [section on i18n](#internationalization-i18n) for details.
|
@@ -521,6 +524,26 @@ Returns ValidResult if and only if provided value is not `Datacaster.absent` (th
|
|
521
524
|
|
522
525
|
I18n keys: `error_key`, `'.any'`, `'datacaster.errors.any'`
|
523
526
|
|
527
|
+
#### `attribute(*keys)`
|
528
|
+
|
529
|
+
Always returns ValidResult. Calls provided method(s) (recursively) on the value and returns their results. `*keys` should be specified in exactly the same manner as in [pick](#pickkeys).
|
530
|
+
|
531
|
+
```ruby
|
532
|
+
class User
|
533
|
+
def login
|
534
|
+
"Alex"
|
535
|
+
end
|
536
|
+
end
|
537
|
+
|
538
|
+
login = Datacaster.schema { attribute(:login) }
|
539
|
+
|
540
|
+
# => Datacaster::ValidResult("Alex")
|
541
|
+
login.(User.new)
|
542
|
+
|
543
|
+
# => Datacaster::ValidResult(#<Datacaster.absent>)
|
544
|
+
login.("test")
|
545
|
+
```
|
546
|
+
|
524
547
|
#### `default(default_value, on: nil)`
|
525
548
|
|
526
549
|
Always returns ValidResult.
|
@@ -631,7 +654,7 @@ mapping.(
|
|
631
654
|
|
632
655
|
See also `#cast_errors` for [error remapping](#error-remapping-cast_errors).
|
633
656
|
|
634
|
-
See also `#pick` for [simpler picking of hash values](#
|
657
|
+
See also `#pick` for [simpler picking of hash values](#pickkeys).
|
635
658
|
|
636
659
|
I18n keys:
|
637
660
|
|
@@ -684,15 +707,18 @@ Returns ValidResult if and only if base returns ValidResult. Returns base's erro
|
|
684
707
|
|
685
708
|
Doesn't transform the value: if base succeeds returns the original value (not the one that base returned).
|
686
709
|
|
687
|
-
#### `pick(
|
710
|
+
#### `pick(*keys)`
|
688
711
|
|
689
712
|
Returns ValidResult if and only if the value `#is_a?(Enumerable)`.
|
690
713
|
|
691
|
-
|
714
|
+
Each argument should be a string, a Symbol, an integer or an array thereof. Each argument plays the role of key(s) to fetch from the value:
|
692
715
|
|
693
|
-
*
|
694
|
-
*
|
695
|
-
|
716
|
+
* If the argument is an Array, value is extracted recursively
|
717
|
+
* Otherwise, `value[argument]` is fetched and added to the result (or `Datacaster.absent` if is is impossible to fetch)
|
718
|
+
|
719
|
+
If only one argument is provided to the `pick`, one fetched value is returned. If several arguments are provided, array is returned wherein each value corresponds to each argument.
|
720
|
+
|
721
|
+
Fetching single key:
|
696
722
|
|
697
723
|
```ruby
|
698
724
|
pick_name = Datacaster.schema { pick(:name) }
|
@@ -703,9 +729,7 @@ pick_name.(last_name: "Johnson") # => Datacaster::ValidResult(#<Datacaster.absen
|
|
703
729
|
pick_name.("test") # => Datacaster::ErrorResult(["is not Enumerable"])
|
704
730
|
```
|
705
731
|
|
706
|
-
|
707
|
-
|
708
|
-
In this case, an array of results is returned, each element in which corresponds to the element in `keys` array (i.e. is an argument of the `pick`) and evaluated in accordance with the above rules.
|
732
|
+
Fetching multiple keys:
|
709
733
|
|
710
734
|
```ruby
|
711
735
|
pick_name_and_age = Datacaster.schema { pick(:name, :age) }
|
@@ -716,6 +740,15 @@ pick_name_and_age.(last_name: "Johnson", age: 20) # => Datacaster::ValidResult([
|
|
716
740
|
pick_name_and_age.("test") # => Datacaster::ErrorResult(["is not Enumerable"])
|
717
741
|
```
|
718
742
|
|
743
|
+
Fetching deeply nested key:
|
744
|
+
|
745
|
+
```ruby
|
746
|
+
nested_hash_picker = Datacaster.schema { pick([:user, :age]) }
|
747
|
+
|
748
|
+
nested_hash_picker.(user: { age: 21 }) # => Datacaster::ValidResult(21)
|
749
|
+
nested_hash_picker.(user: { name: "Alex" }) # => Datacaster::ValidResult(#<Datacaster.absent>)
|
750
|
+
```
|
751
|
+
|
719
752
|
I18n keys:
|
720
753
|
|
721
754
|
* not a Enumerable – `'.must_be'`, `'datacaster.errors.must_be'`.
|
@@ -882,8 +915,6 @@ I18n is performed by ActiveModel gem.
|
|
882
915
|
|
883
916
|
#### `compare(reference_value, error_key = nil)`
|
884
917
|
|
885
|
-
This type is the way to ensure some value in your schema is some predefined "constant".
|
886
|
-
|
887
918
|
Returns ValidResult if and only if `reference_value` equals value.
|
888
919
|
|
889
920
|
```ruby
|
@@ -897,6 +928,50 @@ agreed_with_tos =
|
|
897
928
|
|
898
929
|
I18n keys: `error_key`, `'.compare'`, `'datacaster.errors.compare'`. Adds `reference` i18n variable, setting it to `reference_value.to_s`.
|
899
930
|
|
931
|
+
#### `included_in(*reference_values, error_key: nil)`
|
932
|
+
|
933
|
+
Returns ValidResult if and only if `reference_values.include?` the value.
|
934
|
+
|
935
|
+
I18n keys: `error_key`, `'.included_in'`, `'datacaster.errors.included_in'`. Adds `reference` i18n variable, setting it to `reference_values.map(&:to_s).join(', ')`.
|
936
|
+
|
937
|
+
#### `relate(left, op, right, error_key: nil)`
|
938
|
+
|
939
|
+
Returns ValidResult if and only if `left`, `right` and `op` returns valid result. Doesn't transform the value.
|
940
|
+
|
941
|
+
Use `relate` to check relations between object keys:
|
942
|
+
|
943
|
+
```ruby
|
944
|
+
ordered =
|
945
|
+
# Check that hash[:a] < hash[:b]
|
946
|
+
Datacaster.schema do
|
947
|
+
transform_to_hash(
|
948
|
+
a: relate(:a, :<, :b) & pick(:a),
|
949
|
+
b: pick(:b)
|
950
|
+
)
|
951
|
+
end
|
952
|
+
|
953
|
+
ordered.(a: 1, b: 2)
|
954
|
+
# => Datacaster::ValidResult({:a=>1, :b=>2})
|
955
|
+
|
956
|
+
ordered.(a: 2, b: 1)
|
957
|
+
# => Datacaster::ErrorResult({:a=>["a should be < b"]})
|
958
|
+
|
959
|
+
ordered.({})
|
960
|
+
# => Datacaster::ErrorResult({:a=>["a should be < b"]})
|
961
|
+
```
|
962
|
+
|
963
|
+
Notice that shortcut definitions are available (illustrated in the example above) for the `relate` caster:
|
964
|
+
|
965
|
+
* `:key` provided as 'left' or 'right' argument is exactly the same as `pick(:key)` (works for a string, a symbol or an integer)
|
966
|
+
* `:method` provided as 'op' argument is exactly the same as `check { |(l, r)| l.respond_to?(method) && l.public_send(method, r) }` (works for a string or a symbol)
|
967
|
+
|
968
|
+
Formally, `relate(left, op, right, error_key: error_key)` will:
|
969
|
+
|
970
|
+
* call the `left` caster with the original value, return the result unless it's valid
|
971
|
+
* call the `right` caster with the original value, return the result unless it's valid
|
972
|
+
* call the `op` caster with the `[left_result, right_result]`, return the result unless it's valid
|
973
|
+
* return the original value as valid result
|
974
|
+
|
900
975
|
#### `transform { |value| ... }`
|
901
976
|
|
902
977
|
Always returns ValidResult. Transforms the value: returns whatever the block has returned.
|
@@ -1278,7 +1353,7 @@ Note: in the "root" scope (immediately inside of `schema { ... }` block) the wor
|
|
1278
1353
|
|
1279
1354
|
### Mapping hashes: `transform_to_hash`
|
1280
1355
|
|
1281
|
-
One common task in processing compound data structures is to map one set of hash keys to another set. That's where `transform_to_hash` type comes to play (see also [`pick`](#
|
1356
|
+
One common task in processing compound data structures is to map one set of hash keys to another set. That's where `transform_to_hash` type comes to play (see also [`pick`](#pickkeys) and [`remove`](#remove)).
|
1282
1357
|
|
1283
1358
|
```ruby
|
1284
1359
|
city_with_distance =
|
data/config/locales/en.yml
CHANGED
@@ -11,10 +11,12 @@ en:
|
|
11
11
|
empty: should not be empty
|
12
12
|
float: is not a float
|
13
13
|
hash_value: is not a hash
|
14
|
+
included_in: is not one of %{reference}
|
14
15
|
integer: is not an integer
|
15
16
|
integer32: is not a 32-bit integer
|
16
17
|
iso8601: is not a string with ISO-8601 date and time
|
17
18
|
must_be: "is not %{reference}"
|
19
|
+
relate: "%{left} should be %{op} %{right}"
|
18
20
|
responds_to: "does not respond to %{reference}"
|
19
21
|
string: is not a string
|
20
22
|
to_boolean: does not look like a boolean
|
@@ -17,7 +17,7 @@ module Datacaster
|
|
17
17
|
if right_result.valid?
|
18
18
|
left_result
|
19
19
|
else
|
20
|
-
Datacaster.ErrorResult(
|
20
|
+
Datacaster.ErrorResult(Utils.merge_errors(left_result.raw_errors, right_result.raw_errors))
|
21
21
|
end
|
22
22
|
end
|
23
23
|
end
|
data/lib/datacaster/base.rb
CHANGED
@@ -1,97 +1,5 @@
|
|
1
1
|
module Datacaster
|
2
2
|
class Base
|
3
|
-
|
4
|
-
add_error_to_base = ->(hash, error) {
|
5
|
-
hash[:base] ||= []
|
6
|
-
hash[:base] = merge_errors(hash[:base], error)
|
7
|
-
hash
|
8
|
-
}
|
9
|
-
|
10
|
-
return [] if left.nil? && right.nil?
|
11
|
-
return right if left.nil?
|
12
|
-
return left if right.nil?
|
13
|
-
|
14
|
-
result = case [left.class, right.class]
|
15
|
-
when [Array, Array]
|
16
|
-
left | right
|
17
|
-
when [Array, Hash]
|
18
|
-
add_error_to_base.(right, left)
|
19
|
-
when [Hash, Hash]
|
20
|
-
(left.keys | right.keys).map do |k|
|
21
|
-
[k, merge_errors(left[k], right[k])]
|
22
|
-
end.to_h
|
23
|
-
when [Hash, Array]
|
24
|
-
add_error_to_base.(left, right)
|
25
|
-
else
|
26
|
-
raise ArgumentError.new("Expected failures to be Arrays or Hashes, left: #{left.inspect}, right: #{right.inspect}")
|
27
|
-
end
|
28
|
-
|
29
|
-
result
|
30
|
-
end
|
31
|
-
|
32
|
-
def &(other)
|
33
|
-
AndNode.new(self, other)
|
34
|
-
end
|
35
|
-
|
36
|
-
def |(other)
|
37
|
-
OrNode.new(self, other)
|
38
|
-
end
|
39
|
-
|
40
|
-
def *(other)
|
41
|
-
AndWithErrorAggregationNode.new(self, other)
|
42
|
-
end
|
43
|
-
|
44
|
-
def cast_errors(error_caster)
|
45
|
-
ContextNodes::ErrorsCaster.new(self, error_caster)
|
46
|
-
end
|
47
|
-
|
48
|
-
def then(other)
|
49
|
-
ThenNode.new(self, DefinitionDSL.expand(other))
|
50
|
-
end
|
51
|
-
|
52
|
-
def with_context(context)
|
53
|
-
unless context.is_a?(Hash)
|
54
|
-
raise "with_context expected Hash as argument, got #{context.inspect} instead"
|
55
|
-
end
|
56
|
-
ContextNodes::UserContext.new(self, context)
|
57
|
-
end
|
58
|
-
|
59
|
-
def call(object)
|
60
|
-
call_with_runtime(object, Runtimes::Base.new)
|
61
|
-
end
|
62
|
-
|
63
|
-
def call_with_runtime(object, runtime)
|
64
|
-
result = cast(object, runtime: runtime)
|
65
|
-
unless result.is_a?(Result)
|
66
|
-
raise RuntimeError.new("Caster should've returned Datacaster::Result, but returned #{result.inspect} instead")
|
67
|
-
end
|
68
|
-
result
|
69
|
-
end
|
70
|
-
|
71
|
-
def with_runtime(runtime)
|
72
|
-
->(object) do
|
73
|
-
call_with_runtime(object, runtime)
|
74
|
-
end
|
75
|
-
end
|
76
|
-
|
77
|
-
def i18n_key(*keys, **args)
|
78
|
-
ContextNodes::I18n.new(self, I18nValues::Key.new(keys, args))
|
79
|
-
end
|
80
|
-
|
81
|
-
def i18n_map_keys(mapping)
|
82
|
-
ContextNodes::I18nKeysMapper.new(self, mapping)
|
83
|
-
end
|
84
|
-
|
85
|
-
def i18n_scope(scope, **args)
|
86
|
-
ContextNodes::I18n.new(self, I18nValues::Scope.new(scope, args))
|
87
|
-
end
|
88
|
-
|
89
|
-
def i18n_vars(vars)
|
90
|
-
ContextNodes::I18n.new(self, I18nValues::Scope.new(nil, vars))
|
91
|
-
end
|
92
|
-
|
93
|
-
def inspect
|
94
|
-
"#<Datacaster::Base>"
|
95
|
-
end
|
3
|
+
include Mixin
|
96
4
|
end
|
97
5
|
end
|
data/lib/datacaster/caster.rb
CHANGED
@@ -9,13 +9,13 @@ module Datacaster
|
|
9
9
|
def cast(object, runtime:)
|
10
10
|
result = Runtimes::Base.(runtime, @cast, object)
|
11
11
|
|
12
|
-
|
13
|
-
"should be returned from cast block") unless [Datacaster::Result, Dry::Monads::Result].any? { |k| result.is_a?(k) }
|
14
|
-
|
15
|
-
if result.is_a?(Dry::Monads::Result)
|
12
|
+
if defined?(Dry::Monads::Result) && result.is_a?(Dry::Monads::Result)
|
16
13
|
result = result.success? ? Datacaster.ValidResult(result.value!) : Datacaster.ErrorResult(result.failure)
|
17
14
|
end
|
18
15
|
|
16
|
+
raise TypeError.new("Either Datacaster::Result or Dry::Monads::Result " \
|
17
|
+
"should be returned from cast block") unless result.is_a?(Datacaster::Result)
|
18
|
+
|
19
19
|
result
|
20
20
|
end
|
21
21
|
|
@@ -45,9 +45,9 @@ module Datacaster
|
|
45
45
|
end
|
46
46
|
else
|
47
47
|
if key.is_a?(Array)
|
48
|
-
errors =
|
48
|
+
errors = Utils.merge_errors(errors, key.zip(new_value.raw_errors).to_h)
|
49
49
|
else
|
50
|
-
errors =
|
50
|
+
errors = Utils.merge_errors(errors, {key => new_value.raw_errors})
|
51
51
|
end
|
52
52
|
end
|
53
53
|
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module Datacaster
|
2
|
+
module Mixin
|
3
|
+
def &(other)
|
4
|
+
AndNode.new(self, other)
|
5
|
+
end
|
6
|
+
|
7
|
+
def |(other)
|
8
|
+
OrNode.new(self, other)
|
9
|
+
end
|
10
|
+
|
11
|
+
def *(other)
|
12
|
+
AndWithErrorAggregationNode.new(self, other)
|
13
|
+
end
|
14
|
+
|
15
|
+
def cast_errors(error_caster)
|
16
|
+
ContextNodes::ErrorsCaster.new(self, error_caster)
|
17
|
+
end
|
18
|
+
|
19
|
+
def then(other)
|
20
|
+
ThenNode.new(self, DefinitionDSL.expand(other))
|
21
|
+
end
|
22
|
+
|
23
|
+
def with_context(context)
|
24
|
+
unless context.is_a?(Hash)
|
25
|
+
raise "with_context expected Hash as argument, got #{context.inspect} instead"
|
26
|
+
end
|
27
|
+
ContextNodes::UserContext.new(self, context)
|
28
|
+
end
|
29
|
+
|
30
|
+
def call(object)
|
31
|
+
call_with_runtime(object, Runtimes::Base.new)
|
32
|
+
end
|
33
|
+
|
34
|
+
def call_with_runtime(object, runtime)
|
35
|
+
result = cast(object, runtime: runtime)
|
36
|
+
unless result.is_a?(Result)
|
37
|
+
raise RuntimeError.new("Caster should've returned Datacaster::Result, but returned #{result.inspect} instead")
|
38
|
+
end
|
39
|
+
result
|
40
|
+
end
|
41
|
+
|
42
|
+
def with_runtime(runtime)
|
43
|
+
->(object) do
|
44
|
+
call_with_runtime(object, runtime)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def i18n_key(*keys, **args)
|
49
|
+
ContextNodes::I18n.new(self, I18nValues::Key.new(keys, args))
|
50
|
+
end
|
51
|
+
|
52
|
+
def i18n_map_keys(mapping)
|
53
|
+
ContextNodes::I18nKeysMapper.new(self, mapping)
|
54
|
+
end
|
55
|
+
|
56
|
+
def i18n_scope(scope, **args)
|
57
|
+
ContextNodes::I18n.new(self, I18nValues::Scope.new(scope, args))
|
58
|
+
end
|
59
|
+
|
60
|
+
def i18n_vars(vars)
|
61
|
+
ContextNodes::I18n.new(self, I18nValues::Scope.new(nil, vars))
|
62
|
+
end
|
63
|
+
|
64
|
+
def inspect
|
65
|
+
"#<Datacaster::Base>"
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -89,6 +89,26 @@ module Datacaster
|
|
89
89
|
check { |x| x != Datacaster.absent }.i18n_key(*error_keys)
|
90
90
|
end
|
91
91
|
|
92
|
+
def attribute(*keys)
|
93
|
+
if keys.empty? || keys.any? { |k| !Datacaster::Utils.pickable?(k) }
|
94
|
+
raise RuntimeError, "each argument should be String, Symbol, Integer or an array thereof", caller
|
95
|
+
end
|
96
|
+
|
97
|
+
transform do |input|
|
98
|
+
result =
|
99
|
+
keys.map do |key|
|
100
|
+
Array(key).reduce(input) do |result, k|
|
101
|
+
if result.respond_to?(k)
|
102
|
+
result.public_send(k)
|
103
|
+
else
|
104
|
+
break Datacaster.absent
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
keys.length == 1 ? result.first : result
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
92
112
|
def default(value, on: nil)
|
93
113
|
transform do |x|
|
94
114
|
if x == Datacaster.absent ||
|
@@ -100,55 +120,120 @@ module Datacaster
|
|
100
120
|
end
|
101
121
|
end
|
102
122
|
|
103
|
-
def
|
104
|
-
|
105
|
-
end
|
106
|
-
|
107
|
-
def remove
|
108
|
-
transform { Datacaster.absent }
|
123
|
+
def merge_message_keys(*keys)
|
124
|
+
MessageKeysMerger.new(keys)
|
109
125
|
end
|
110
126
|
|
111
|
-
def
|
112
|
-
|
127
|
+
def must_be(klass, error_key = nil)
|
128
|
+
error_keys = ['.must_be', 'datacaster.errors.must_be']
|
129
|
+
error_keys.unshift(error_key) if error_key
|
130
|
+
check { |x| x.is_a?(klass) }.i18n_key(*error_keys, reference: klass.name)
|
113
131
|
end
|
114
132
|
|
115
|
-
def
|
116
|
-
|
117
|
-
|
118
|
-
|
133
|
+
def optional(base, on: nil)
|
134
|
+
return absent | base if on == nil
|
135
|
+
cast do |x|
|
136
|
+
if x == Datacaster.absent ||
|
137
|
+
(!on.nil? && x.respond_to?(on) && x.public_send(on))
|
138
|
+
Datacaster.ValidResult(Datacaster.absent)
|
119
139
|
else
|
120
|
-
|
140
|
+
base.(x)
|
121
141
|
end
|
122
|
-
on_clauses.reduce(switch) do |result, (k, v)|
|
123
|
-
result.on(k, v)
|
124
142
|
end
|
125
143
|
end
|
126
144
|
|
145
|
+
def pass
|
146
|
+
transform(&:itself)
|
147
|
+
end
|
148
|
+
|
127
149
|
def pass_if(base)
|
128
150
|
ContextNodes::PassIf.new(base)
|
129
151
|
end
|
130
152
|
|
131
|
-
def pick(*keys
|
132
|
-
|
153
|
+
def pick(*keys)
|
154
|
+
if keys.empty? || keys.any? { |k| !Datacaster::Utils.pickable?(k) }
|
155
|
+
raise RuntimeError, "each argument should be String, Symbol, Integer or an array thereof", caller
|
156
|
+
end
|
133
157
|
|
134
|
-
|
158
|
+
retrieve_key = -> (from, key) do
|
159
|
+
if from.respond_to?(:key?) && !from.key?(key)
|
160
|
+
Datacaster.absent
|
161
|
+
elsif from.respond_to?(:length) && key.is_a?(Integer) && key > 0 && key >= from.length
|
162
|
+
Datacaster.absent
|
163
|
+
elsif !from.respond_to?(:[])
|
164
|
+
Datacaster.absent
|
165
|
+
else
|
166
|
+
from[key]
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
must_be(Enumerable) & transform { |input|
|
135
171
|
result =
|
136
172
|
keys.map do |key|
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
else
|
142
|
-
value[key]
|
173
|
+
Array(key).reduce(input) do |result, k|
|
174
|
+
result = retrieve_key.(result, k)
|
175
|
+
break result if result == Datacaster.absent
|
176
|
+
result
|
143
177
|
end
|
144
178
|
end
|
145
|
-
|
146
179
|
keys.length == 1 ? result.first : result
|
147
180
|
}
|
148
181
|
end
|
149
182
|
|
150
|
-
def
|
151
|
-
|
183
|
+
def relate(left, op, right, error_key: nil)
|
184
|
+
error_keys = ['.relate', 'datacaster.errors.relate']
|
185
|
+
additional_vars = {}
|
186
|
+
|
187
|
+
left_caster = left
|
188
|
+
if Datacaster::Utils.pickable?(left)
|
189
|
+
left_caster = pick(left)
|
190
|
+
elsif !Datacaster.instance?(left)
|
191
|
+
raise RuntimeError, "provide String, Symbol, Integer or array thereof instead of #{left.inspect}", caller
|
192
|
+
end
|
193
|
+
|
194
|
+
right_caster = right
|
195
|
+
if Datacaster::Utils.pickable?(right)
|
196
|
+
right_caster = pick(right)
|
197
|
+
elsif !Datacaster.instance?(right)
|
198
|
+
raise RuntimeError, "provide String, Symbol, Integer or array thereof instead of #{right.inspect}", caller
|
199
|
+
end
|
200
|
+
|
201
|
+
op_caster = op
|
202
|
+
if op.is_a?(String) || op.is_a?(Symbol)
|
203
|
+
op_caster = check { |(l, r)| l.respond_to?(op) && l.public_send(op, r) }
|
204
|
+
elsif !Datacaster.instance?(left)
|
205
|
+
raise RuntimeError, "provide String or Symbol instead of #{op.inspect}", caller
|
206
|
+
end
|
207
|
+
|
208
|
+
{left: left, op: op, right: right}.each do |k, v|
|
209
|
+
if [String, Symbol, Integer].any? { |c| v.is_a?(c) }
|
210
|
+
additional_vars[k] = v
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
if additional_vars.length == 3
|
215
|
+
error_keys.unshift(".#{left}.#{op}.#{right}")
|
216
|
+
end
|
217
|
+
error_keys.unshift(error_key) if error_key
|
218
|
+
|
219
|
+
cast do |value|
|
220
|
+
left_result = left_caster.(value)
|
221
|
+
next left_result unless left_result.valid?
|
222
|
+
i18n_var!(:left, left_result.value) unless additional_vars.key?(:left)
|
223
|
+
|
224
|
+
right_result = right_caster.(value)
|
225
|
+
next right_result unless right_result.valid?
|
226
|
+
i18n_var!(:right, right_result.value) unless additional_vars.key?(:right)
|
227
|
+
|
228
|
+
result = op_caster.([left_result.value, right_result.value])
|
229
|
+
next Datacaster.ErrorResult([I18nValues::Key.new(error_keys)]) unless result.valid?
|
230
|
+
|
231
|
+
Datacaster.ValidResult(value)
|
232
|
+
end.i18n_vars(additional_vars)
|
233
|
+
end
|
234
|
+
|
235
|
+
def remove
|
236
|
+
transform { Datacaster.absent }
|
152
237
|
end
|
153
238
|
|
154
239
|
def responds_to(method, error_key = nil)
|
@@ -157,22 +242,15 @@ module Datacaster
|
|
157
242
|
check { |x| x.respond_to?(method) }.i18n_key(*error_keys, reference: method.to_s)
|
158
243
|
end
|
159
244
|
|
160
|
-
def
|
161
|
-
|
162
|
-
|
163
|
-
|
245
|
+
def switch(base = nil, **on_clauses)
|
246
|
+
switch = SwitchNode.new(base)
|
247
|
+
on_clauses.reduce(switch) do |result, (k, v)|
|
248
|
+
result.on(k, v)
|
249
|
+
end
|
164
250
|
end
|
165
251
|
|
166
|
-
def
|
167
|
-
|
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
|
252
|
+
def transform_to_value(value)
|
253
|
+
transform { value }
|
176
254
|
end
|
177
255
|
|
178
256
|
# Strict types
|
@@ -205,13 +283,19 @@ module Datacaster
|
|
205
283
|
def hash_value(error_key = nil)
|
206
284
|
error_keys = ['.hash_value', 'datacaster.errors.hash_value']
|
207
285
|
error_keys.unshift(error_key) if error_key
|
208
|
-
check
|
286
|
+
check { |x| x.is_a?(Hash) }.i18n_key(*error_keys)
|
209
287
|
end
|
210
288
|
|
211
289
|
def hash_with_symbolized_keys(error_key = nil)
|
212
290
|
hash_value(error_key) & transform { |x| x.symbolize_keys }
|
213
291
|
end
|
214
292
|
|
293
|
+
def included_in(*values, error_key: nil)
|
294
|
+
error_keys = ['.included_in', 'datacaster.errors.included_in']
|
295
|
+
error_keys.unshift(error_key) if error_key
|
296
|
+
check { |x| values.include?(x) }.i18n_key(*error_keys, reference: values.map(&:to_s).join(', '))
|
297
|
+
end
|
298
|
+
|
215
299
|
def integer(error_key = nil)
|
216
300
|
error_keys = ['.integer', 'datacaster.errors.integer']
|
217
301
|
error_keys.unshift(error_key) if error_key
|
@@ -1,16 +1,14 @@
|
|
1
1
|
module Datacaster
|
2
2
|
class SwitchNode < Base
|
3
3
|
def initialize(base = nil, on_casters = [], else_caster = nil)
|
4
|
-
base = base
|
4
|
+
@base = base
|
5
5
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
@base = Datacaster::Predefined.pick(base)
|
13
|
-
else
|
6
|
+
if Datacaster::Utils.pickable?(@base)
|
7
|
+
@base = Datacaster::Predefined.pick(@base)
|
8
|
+
end
|
9
|
+
|
10
|
+
if !@base.nil? && !Datacaster.instance?(@base)
|
11
|
+
puts @base.inspect
|
14
12
|
raise RuntimeError, "provide a Datacaster::Base instance, a hash key, or an array of keys to switch(...) caster", caller
|
15
13
|
end
|
16
14
|
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Datacaster
|
2
|
+
module Utils
|
3
|
+
extend self
|
4
|
+
|
5
|
+
def merge_errors(left, right)
|
6
|
+
add_error_to_base = ->(hash, error) {
|
7
|
+
hash[:base] ||= []
|
8
|
+
hash[:base] = merge_errors(hash[:base], error)
|
9
|
+
hash
|
10
|
+
}
|
11
|
+
|
12
|
+
return [] if left.nil? && right.nil?
|
13
|
+
return right if left.nil?
|
14
|
+
return left if right.nil?
|
15
|
+
|
16
|
+
result = case [left.class, right.class]
|
17
|
+
when [Array, Array]
|
18
|
+
left | right
|
19
|
+
when [Array, Hash]
|
20
|
+
add_error_to_base.(right, left)
|
21
|
+
when [Hash, Hash]
|
22
|
+
(left.keys | right.keys).map do |k|
|
23
|
+
[k, merge_errors(left[k], right[k])]
|
24
|
+
end.to_h
|
25
|
+
when [Hash, Array]
|
26
|
+
add_error_to_base.(left, right)
|
27
|
+
else
|
28
|
+
raise ArgumentError.new("Expected failures to be Arrays or Hashes, left: #{left.inspect}, right: #{right.inspect}")
|
29
|
+
end
|
30
|
+
|
31
|
+
result
|
32
|
+
end
|
33
|
+
|
34
|
+
def pickable?(value)
|
35
|
+
is_literal = ->(v) { [String, Symbol, Integer].any? { |c| v.is_a?(c) } }
|
36
|
+
is_literal.(value) ||
|
37
|
+
value.is_a?(Array) && !value.empty? && value.all? { |v| is_literal.(v) }
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
data/lib/datacaster/version.rb
CHANGED
data/lib/datacaster.rb
CHANGED
@@ -24,6 +24,10 @@ module Datacaster
|
|
24
24
|
Datacaster::Absent.instance
|
25
25
|
end
|
26
26
|
|
27
|
+
def instance?(object)
|
28
|
+
object.is_a?(Mixin)
|
29
|
+
end
|
30
|
+
|
27
31
|
private
|
28
32
|
|
29
33
|
def build_schema(i18n_scope: nil, &block)
|
@@ -31,7 +35,7 @@ module Datacaster
|
|
31
35
|
|
32
36
|
datacaster = DefinitionDSL.eval(&block)
|
33
37
|
|
34
|
-
unless
|
38
|
+
unless Datacaster.instance?(datacaster)
|
35
39
|
raise "Datacaster instance should be returned from a block (e.g. result of 'hash_schema(...)' call)"
|
36
40
|
end
|
37
41
|
|
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.1.
|
4
|
+
version: 3.1.3
|
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-10-
|
11
|
+
date: 2023-10-06 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activemodel
|
@@ -150,6 +150,7 @@ files:
|
|
150
150
|
- lib/datacaster/i18n_values/key.rb
|
151
151
|
- lib/datacaster/i18n_values/scope.rb
|
152
152
|
- lib/datacaster/message_keys_merger.rb
|
153
|
+
- lib/datacaster/mixin.rb
|
153
154
|
- lib/datacaster/or_node.rb
|
154
155
|
- lib/datacaster/predefined.rb
|
155
156
|
- lib/datacaster/result.rb
|
@@ -162,6 +163,7 @@ files:
|
|
162
163
|
- lib/datacaster/then_node.rb
|
163
164
|
- lib/datacaster/transformer.rb
|
164
165
|
- lib/datacaster/trier.rb
|
166
|
+
- lib/datacaster/utils.rb
|
165
167
|
- lib/datacaster/validator.rb
|
166
168
|
- lib/datacaster/version.rb
|
167
169
|
homepage: https://github.com/EugZol/datacaster
|