datacaster 3.1.1 → 3.1.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d563422d81d874ec9f74b4e95a3d24a7a59cf2c3f50ad709a226346616364882
4
- data.tar.gz: e33c3dffdbff37e5ce711f56c0196e8955379764c98c2429ec391fd33819797d
3
+ metadata.gz: 22f1741420c172f91fba9bf20a341a785f609a7e0132e74bcd3a07e6069eaca4
4
+ data.tar.gz: 641fb863b7a40931f481d066e09062cddff1a4e3ff642d5981dacf9bfaa036eb
5
5
  SHA512:
6
- metadata.gz: d01eb486e4d11954f672e6958803b7ea4c60430c58850f2b4c8e8b16a500229c28a91ecadc45317775f7ff8098c4beddfb0373d415f72e5c917ccb1a93f589c8
7
- data.tar.gz: c9bdb6f156a7de6eb81fa9cb87dcbf050fd47292d417afd4d085cb64a9e5460c4022b6471de723a1e112cd730a5b12e14c595e9ce248e30ff18fd84625a2cc8a
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
- #=> Datacaster::ErrorResult(['should be an array'])
180
+ # => Datacaster::ErrorResult(['should be an array'])
179
181
 
180
182
  # In reality
181
- #=> <Datacaster::ErrorResult([#<Datacaster::I18nValues::Key(.array, datacaster.errors.array) {:value=>nil}>])>
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.
@@ -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(self.class.merge_errors(left_result.raw_errors, right_result.raw_errors))
20
+ Datacaster.ErrorResult(Utils.merge_errors(left_result.raw_errors, right_result.raw_errors))
21
21
  end
22
22
  end
23
23
  end
@@ -1,97 +1,5 @@
1
1
  module Datacaster
2
2
  class Base
3
- def self.merge_errors(left, right)
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
@@ -9,13 +9,13 @@ module Datacaster
9
9
  def cast(object, runtime:)
10
10
  result = Runtimes::Base.(runtime, @cast, object)
11
11
 
12
- raise TypeError.new("Either Datacaster::Result or Dry::Monads::Result " \
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 = self.class.merge_errors(errors, key.zip(new_value.raw_errors).to_h)
48
+ errors = Utils.merge_errors(errors, key.zip(new_value.raw_errors).to_h)
49
49
  else
50
- errors = self.class.merge_errors(errors, {key => new_value.raw_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(error_key) { |x| x.is_a?(Hash) }
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
@@ -1,3 +1,3 @@
1
1
  module Datacaster
2
- VERSION = "3.1.1"
2
+ VERSION = "3.1.2"
3
3
  end
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 datacaster.is_a?(Base)
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.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-02 00:00:00.000000000 Z
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