datacaster 3.1.1 → 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 +48 -4
- 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 +53 -1
- 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
@@ -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.
|
@@ -882,8 +884,6 @@ I18n is performed by ActiveModel gem.
|
|
882
884
|
|
883
885
|
#### `compare(reference_value, error_key = nil)`
|
884
886
|
|
885
|
-
This type is the way to ensure some value in your schema is some predefined "constant".
|
886
|
-
|
887
887
|
Returns ValidResult if and only if `reference_value` equals value.
|
888
888
|
|
889
889
|
```ruby
|
@@ -897,6 +897,50 @@ agreed_with_tos =
|
|
897
897
|
|
898
898
|
I18n keys: `error_key`, `'.compare'`, `'datacaster.errors.compare'`. Adds `reference` i18n variable, setting it to `reference_value.to_s`.
|
899
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
|
+
|
900
944
|
#### `transform { |value| ... }`
|
901
945
|
|
902
946
|
Always returns ValidResult. Transforms the value: returns whatever the block has returned.
|
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
|
@@ -104,6 +104,52 @@ module Datacaster
|
|
104
104
|
transform { value }
|
105
105
|
end
|
106
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
|
+
|
107
153
|
def remove
|
108
154
|
transform { Datacaster.absent }
|
109
155
|
end
|
@@ -205,13 +251,19 @@ module Datacaster
|
|
205
251
|
def hash_value(error_key = nil)
|
206
252
|
error_keys = ['.hash_value', 'datacaster.errors.hash_value']
|
207
253
|
error_keys.unshift(error_key) if error_key
|
208
|
-
check
|
254
|
+
check { |x| x.is_a?(Hash) }.i18n_key(*error_keys)
|
209
255
|
end
|
210
256
|
|
211
257
|
def hash_with_symbolized_keys(error_key = nil)
|
212
258
|
hash_value(error_key) & transform { |x| x.symbolize_keys }
|
213
259
|
end
|
214
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
|
+
|
215
267
|
def integer(error_key = nil)
|
216
268
|
error_keys = ['.integer', 'datacaster.errors.integer']
|
217
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
|