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 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