datacaster 3.1.0 → 3.1.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +75 -17
- 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 +76 -6
- data/lib/datacaster/utils.rb +34 -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: 22f1741420c172f91fba9bf20a341a785f609a7e0132e74bcd3a07e6069eaca4
|
4
|
+
data.tar.gz: 641fb863b7a40931f481d066e09062cddff1a4e3ff642d5981dacf9bfaa036eb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2a3573aac6a44ef0a5849da04325c9089119ad529cc6f6384d18bb6dda97f66358081c27d1fc260d95f7adaec5937bda7769cd9207ea9be6b01f2547f4425659
|
7
|
+
data.tar.gz: '04765854fbe156b3418728d4d2392b78ce0837ead8e1e72b76a51d55e6227bb68eebf8a98e672c924205bbe731610f21c72a1be355c51c2f014886ab160efdb7'
|
data/README.md
CHANGED
@@ -32,12 +32,12 @@ It is currently used in production in several projects (mainly as request parame
|
|
32
32
|
- [`integer32(error_key = nil)`](#integer32error_key--nil)
|
33
33
|
- [`non_empty_string(error_key = nil)`](#non_empty_stringerror_key--nil)
|
34
34
|
- [Special types](#special-types)
|
35
|
-
- [`absent(error_key = nil)`](#absenterror_key--nil)
|
35
|
+
- [`absent(error_key = nil, on: nil)`](#absenterror_key--nil-on-nil)
|
36
36
|
- [`any(error_key = nil)`](#anyerror_key--nil)
|
37
37
|
- [`default(default_value, on: nil)`](#defaultdefault_value-on-nil)
|
38
38
|
- [`merge_message_keys(*keys)`](#merge_message_keyskeys)
|
39
39
|
- [`must_be(klass, error_key = nil)`](#must_beklass-error_key--nil)
|
40
|
-
- [`optional(base)`](#optionalbase)
|
40
|
+
- [`optional(base, on: nil)`](#optionalbase-on-nil)
|
41
41
|
- [`pass`](#pass)
|
42
42
|
- [`pass_if(base)`](#pass_ifbase)
|
43
43
|
- [`pick(key)`](#pickkey)
|
@@ -56,6 +56,8 @@ It is currently used in production in several projects (mainly as request parame
|
|
56
56
|
- [`try(error_key = nil, catched_exception:) { |value| ... }`](#tryerror_key--nil-catched_exception--value--)
|
57
57
|
- [`validate(active_model_validations, name = 'Anonymous')`](#validateactive_model_validations-name--anonymous)
|
58
58
|
- [`compare(reference_value, error_key = nil)`](#comparereference_value-error_key--nil)
|
59
|
+
- [`included_in(*reference_values, error_key: nil)`](#included_inreference_values-error_key-nil)
|
60
|
+
- [`relate(left, op, right, error_key: nil)`](#relateleft-op-right-error_key-nil)
|
59
61
|
- [`transform { |value| ... }`](#transform--value--)
|
60
62
|
- [`transform_if_present { |value| ... }`](#transform_if_present--value--)
|
61
63
|
- [Array schemas](#array-schemas)
|
@@ -175,10 +177,10 @@ array = Datacaster.schema { array }
|
|
175
177
|
array.(nil)
|
176
178
|
|
177
179
|
# In this README
|
178
|
-
|
180
|
+
# => Datacaster::ErrorResult(['should be an array'])
|
179
181
|
|
180
182
|
# In reality
|
181
|
-
|
183
|
+
# => <Datacaster::ErrorResult([#<Datacaster::I18nValues::Key(.array, datacaster.errors.array) {:value=>nil}>])>
|
182
184
|
```
|
183
185
|
|
184
186
|
See [section on i18n](#internationalization-i18n) for details.
|
@@ -502,9 +504,16 @@ Returns ValidResult if and only if provided value is a string and is not empty.
|
|
502
504
|
|
503
505
|
### Special types
|
504
506
|
|
505
|
-
#### `absent(error_key = nil)`
|
507
|
+
#### `absent(error_key = nil, on: nil)`
|
506
508
|
|
507
|
-
Returns ValidResult if and only if provided value is
|
509
|
+
Returns ValidResult if and only if provided value is absent. Relevant only for hash schemas (see below). Transforms the value to `Datacaster.absent`.
|
510
|
+
|
511
|
+
The value is considered absent:
|
512
|
+
|
513
|
+
* if the value is `Datacaster.absent` (`on` is disregarded in such case)
|
514
|
+
* if `on` is set to a method name to which the value responds and yields truthy
|
515
|
+
|
516
|
+
Set `on` to `:nil?`, `:empty?` or similar method names.
|
508
517
|
|
509
518
|
I18n keys: `error_key`, `'.absent'`, `'datacaster.errors.absent'`.
|
510
519
|
|
@@ -636,18 +645,25 @@ Returns ValidResult if and only if the value `#is_a?(klass)`. Doesn't transform
|
|
636
645
|
|
637
646
|
I18n keys: `error_key`, `'.must_be'`, `'datacaster.errors.must_be'`. Adds `reference` i18n variable, setting it to `klass.name`.
|
638
647
|
|
639
|
-
#### `optional(base)`
|
648
|
+
#### `optional(base, on: nil)`
|
640
649
|
|
641
|
-
Returns ValidResult if and only if the value is either
|
650
|
+
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.
|
651
|
+
|
652
|
+
Value is considered absent:
|
653
|
+
|
654
|
+
* if the value is `Datacaster.absent` (`on` is disregarded in such case)
|
655
|
+
* if `on` is set to a method name to which the value responds and yields truthy
|
656
|
+
|
657
|
+
Set `on` to `:nil?`, `:empty?` or similar method names.
|
642
658
|
|
643
659
|
```ruby
|
644
660
|
item_with_optional_price =
|
645
|
-
|
646
|
-
|
647
|
-
|
648
|
-
|
649
|
-
|
650
|
-
|
661
|
+
Datacaster.schema do
|
662
|
+
hash_schema(
|
663
|
+
name: string,
|
664
|
+
price: optional(float)
|
665
|
+
)
|
666
|
+
end
|
651
667
|
|
652
668
|
item_with_optional_price.(name: "Book", price: 1.23)
|
653
669
|
# => Datacaster::ValidResult({:name=>"Book", :price=>1.23})
|
@@ -868,8 +884,6 @@ I18n is performed by ActiveModel gem.
|
|
868
884
|
|
869
885
|
#### `compare(reference_value, error_key = nil)`
|
870
886
|
|
871
|
-
This type is the way to ensure some value in your schema is some predefined "constant".
|
872
|
-
|
873
887
|
Returns ValidResult if and only if `reference_value` equals value.
|
874
888
|
|
875
889
|
```ruby
|
@@ -883,6 +897,50 @@ agreed_with_tos =
|
|
883
897
|
|
884
898
|
I18n keys: `error_key`, `'.compare'`, `'datacaster.errors.compare'`. Adds `reference` i18n variable, setting it to `reference_value.to_s`.
|
885
899
|
|
900
|
+
#### `included_in(*reference_values, error_key: nil)`
|
901
|
+
|
902
|
+
Returns ValidResult if and only if `reference_values.include?` the value.
|
903
|
+
|
904
|
+
I18n keys: `error_key`, `'.included_in'`, `'datacaster.errors.included_in'`. Adds `reference` i18n variable, setting it to `reference_values.map(&:to_s).join(', ')`.
|
905
|
+
|
906
|
+
#### `relate(left, op, right, error_key: nil)`
|
907
|
+
|
908
|
+
Returns ValidResult if and only if `left`, `right` and `op` returns valid result. Doesn't transform the value.
|
909
|
+
|
910
|
+
Use `relate` to check relations between object keys:
|
911
|
+
|
912
|
+
```ruby
|
913
|
+
ordered =
|
914
|
+
# Check that hash[:a] < hash[:b]
|
915
|
+
Datacaster.schema do
|
916
|
+
transform_to_hash(
|
917
|
+
a: relate(:a, :<, :b) & pick(:a),
|
918
|
+
b: pick(:b)
|
919
|
+
)
|
920
|
+
end
|
921
|
+
|
922
|
+
ordered.(a: 1, b: 2)
|
923
|
+
# => Datacaster::ValidResult({:a=>1, :b=>2})
|
924
|
+
|
925
|
+
ordered.(a: 2, b: 1)
|
926
|
+
# => Datacaster::ErrorResult({:a=>["a should be < b"]})
|
927
|
+
|
928
|
+
ordered.({})
|
929
|
+
# => Datacaster::ErrorResult({:a=>["a should be < b"]})
|
930
|
+
```
|
931
|
+
|
932
|
+
Notice that shortcut definitions are available (illustrated in the example above) for the `relate` caster:
|
933
|
+
|
934
|
+
* `:key` provided as 'left' or 'right' argument is exactly the same as `pick(:key)` (works for a string, a symbol or an integer)
|
935
|
+
* `: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)
|
936
|
+
|
937
|
+
Formally, `relate(left, op, right, error_key: error_key)` will:
|
938
|
+
|
939
|
+
* call the `left` caster with the original value, return the result unless it's valid
|
940
|
+
* call the `right` caster with the original value, return the result unless it's valid
|
941
|
+
* call the `op` caster with the `[left_result, right_result]`, return the result unless it's valid
|
942
|
+
* return the original value as valid result
|
943
|
+
|
886
944
|
#### `transform { |value| ... }`
|
887
945
|
|
888
946
|
Always returns ValidResult. Transforms the value: returns whatever the block has returned.
|
@@ -1025,7 +1083,7 @@ restricted_params.(username: "test", is_admin: nil)
|
|
1025
1083
|
|
1026
1084
|
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.
|
1027
1085
|
|
1028
|
-
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):
|
1086
|
+
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):
|
1029
1087
|
|
1030
1088
|
```ruby
|
1031
1089
|
person =
|
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
|
@@ -67,10 +67,20 @@ module Datacaster
|
|
67
67
|
|
68
68
|
# 'Meta' types
|
69
69
|
|
70
|
-
def absent(error_key = nil)
|
70
|
+
def absent(error_key = nil, on: nil)
|
71
71
|
error_keys = ['.absent', 'datacaster.errors.absent']
|
72
72
|
error_keys.unshift(error_key) if error_key
|
73
|
-
|
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
|
74
84
|
end
|
75
85
|
|
76
86
|
def any(error_key = nil)
|
@@ -82,7 +92,7 @@ module Datacaster
|
|
82
92
|
def default(value, on: nil)
|
83
93
|
transform do |x|
|
84
94
|
if x == Datacaster.absent ||
|
85
|
-
(on && x.respond_to?(on) && x.public_send(on))
|
95
|
+
(!on.nil? && x.respond_to?(on) && x.public_send(on))
|
86
96
|
value
|
87
97
|
else
|
88
98
|
x
|
@@ -94,6 +104,52 @@ module Datacaster
|
|
94
104
|
transform { value }
|
95
105
|
end
|
96
106
|
|
107
|
+
# min_amount: has_relation(:min_amount, :>, :max_amount)
|
108
|
+
|
109
|
+
def relate(left, op, right, error_key: nil)
|
110
|
+
error_keys = ['.relate', 'datacaster.errors.relate']
|
111
|
+
additional_vars = {}
|
112
|
+
|
113
|
+
{left: left, op: op, right: right}.each do |k, v|
|
114
|
+
if [String, Symbol, Integer].any? { |c| v.is_a?(c) }
|
115
|
+
additional_vars[k] = v
|
116
|
+
elsif !Datacaster.instance?(v)
|
117
|
+
raise RuntimeError, "expected #{k} to be String, Symbol, Integer or Datacaster::Base, but got #{v.inspect}", caller
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
if op.is_a?(Integer)
|
122
|
+
raise RuntimeError, "expected op to be String, Symbol or Datacaster::Base, but got #{op.inspect}", caller
|
123
|
+
end
|
124
|
+
|
125
|
+
if [left, op, right].none? { |x| Datacaster.instance?(x) }
|
126
|
+
error_keys.unshift(".#{left}.#{op}.#{right}")
|
127
|
+
end
|
128
|
+
error_keys.unshift(error_key) if error_key
|
129
|
+
|
130
|
+
left = pick(left) unless Datacaster.instance?(left)
|
131
|
+
right = pick(right) unless Datacaster.instance?(right)
|
132
|
+
op_caster = op
|
133
|
+
unless Datacaster.instance?(op_caster)
|
134
|
+
op_caster = check { |(l, r)| l.respond_to?(op) && l.public_send(op, r) }
|
135
|
+
end
|
136
|
+
|
137
|
+
cast do |value|
|
138
|
+
left_result = left.(value)
|
139
|
+
next left_result unless left_result.valid?
|
140
|
+
i18n_var!(:left, left_result.value) unless additional_vars.key?(:left)
|
141
|
+
|
142
|
+
right_result = right.(value)
|
143
|
+
next right_result unless right_result.valid?
|
144
|
+
i18n_var!(:right, right_result.value) unless additional_vars.key?(:right)
|
145
|
+
|
146
|
+
result = op_caster.([left_result.value, right_result.value])
|
147
|
+
next Datacaster.ErrorResult([I18nValues::Key.new(error_keys)]) unless result.valid?
|
148
|
+
|
149
|
+
Datacaster.ValidResult(value)
|
150
|
+
end.i18n_vars(additional_vars)
|
151
|
+
end
|
152
|
+
|
97
153
|
def remove
|
98
154
|
transform { Datacaster.absent }
|
99
155
|
end
|
@@ -153,8 +209,16 @@ module Datacaster
|
|
153
209
|
check { |x| x.is_a?(klass) }.i18n_key(*error_keys, reference: klass.name)
|
154
210
|
end
|
155
211
|
|
156
|
-
def optional(base)
|
157
|
-
absent | base
|
212
|
+
def optional(base, on: nil)
|
213
|
+
return absent | base if on == nil
|
214
|
+
cast do |x|
|
215
|
+
if x == Datacaster.absent ||
|
216
|
+
(!on.nil? && x.respond_to?(on) && x.public_send(on))
|
217
|
+
Datacaster.ValidResult(Datacaster.absent)
|
218
|
+
else
|
219
|
+
base.(x)
|
220
|
+
end
|
221
|
+
end
|
158
222
|
end
|
159
223
|
|
160
224
|
# Strict types
|
@@ -187,13 +251,19 @@ module Datacaster
|
|
187
251
|
def hash_value(error_key = nil)
|
188
252
|
error_keys = ['.hash_value', 'datacaster.errors.hash_value']
|
189
253
|
error_keys.unshift(error_key) if error_key
|
190
|
-
check
|
254
|
+
check { |x| x.is_a?(Hash) }.i18n_key(*error_keys)
|
191
255
|
end
|
192
256
|
|
193
257
|
def hash_with_symbolized_keys(error_key = nil)
|
194
258
|
hash_value(error_key) & transform { |x| x.symbolize_keys }
|
195
259
|
end
|
196
260
|
|
261
|
+
def included_in(*values, error_key: nil)
|
262
|
+
error_keys = ['.included_in', 'datacaster.errors.included_in']
|
263
|
+
error_keys.unshift(error_key) if error_key
|
264
|
+
check { |x| values.include?(x) }.i18n_key(*error_keys, reference: values.map(&:to_s).join(', '))
|
265
|
+
end
|
266
|
+
|
197
267
|
def integer(error_key = nil)
|
198
268
|
error_keys = ['.integer', 'datacaster.errors.integer']
|
199
269
|
error_keys.unshift(error_key) if error_key
|
@@ -0,0 +1,34 @@
|
|
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
|
+
end
|
34
|
+
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.2
|
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-05 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
|