datacaster 3.1.0 → 3.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|