datacaster 3.1.2 → 3.1.5

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: 22f1741420c172f91fba9bf20a341a785f609a7e0132e74bcd3a07e6069eaca4
4
- data.tar.gz: 641fb863b7a40931f481d066e09062cddff1a4e3ff642d5981dacf9bfaa036eb
3
+ metadata.gz: 29c3f6c825f2c2d9a664e370079cde58df4ce9685d34d8428fb29aa7b98c52f3
4
+ data.tar.gz: 7d90ad442a4bd32ae4526d403629676c3aec9566973486bacd6565e8cb3d7bbd
5
5
  SHA512:
6
- metadata.gz: 2a3573aac6a44ef0a5849da04325c9089119ad529cc6f6384d18bb6dda97f66358081c27d1fc260d95f7adaec5937bda7769cd9207ea9be6b01f2547f4425659
7
- data.tar.gz: '04765854fbe156b3418728d4d2392b78ce0837ead8e1e72b76a51d55e6227bb68eebf8a98e672c924205bbe731610f21c72a1be355c51c2f014886ab160efdb7'
6
+ metadata.gz: acc11fefd17414ba1400bd98d2105b194301ac63f4d6b4204f001f918984326ab711c92d89133b475fd09d51b10c5d6427f6d6f516742a4ceb0a3eb2cf07f983
7
+ data.tar.gz: 07f288ab4e44e0b7a931aa43b34bac1a98d34d68f9d72241fc34f1648aeedc0412b51bdbe1113d9c34674a283e5fc6c559d4d2c5c385f5ec55c65d3b8345fb73
data/README.md CHANGED
@@ -34,13 +34,14 @@ It is currently used in production in several projects (mainly as request parame
34
34
  - [Special types](#special-types)
35
35
  - [`absent(error_key = nil, on: nil)`](#absenterror_key--nil-on-nil)
36
36
  - [`any(error_key = nil)`](#anyerror_key--nil)
37
+ - [`attribute(*keys)`](#attributekeys)
37
38
  - [`default(default_value, on: nil)`](#defaultdefault_value-on-nil)
38
39
  - [`merge_message_keys(*keys)`](#merge_message_keyskeys)
39
40
  - [`must_be(klass, error_key = nil)`](#must_beklass-error_key--nil)
40
41
  - [`optional(base, on: nil)`](#optionalbase-on-nil)
41
42
  - [`pass`](#pass)
42
43
  - [`pass_if(base)`](#pass_ifbase)
43
- - [`pick(key)`](#pickkey)
44
+ - [`pick(*keys)`](#pickkeys)
44
45
  - [`remove`](#remove)
45
46
  - [`responds_to(method, error_key = nil)`](#responds_tomethod-error_key--nil)
46
47
  - [`transform_to_value(value)`](#transform_to_valuevalue)
@@ -414,6 +415,7 @@ Notice that shortcut definitions are available (illustrated in the example above
414
415
 
415
416
  * `switch(:key)` is exactly the same as `switch(pick(:key))` (works for a string, a symbol, or an array thereof)
416
417
  * `on(:key, ...)` is exactly the same as `on(compare(:key), ...)` (works for a string or a symbol)
418
+ * `on(:key, ...)` will match on `:key` and `'key'` value, and the same is true for `on('key', ...)` (to disable that behavior provide `strict: true` keyword arg: `on('key', ..., strict: true)`)
417
419
  * `switch([caster], on_check => on_caster, ...)` is exactly the same as `switch([caster]).on(on_check, on_caster).on(...)`
418
420
 
419
421
  `switch()` without a `base` argument will pass the incoming value to the `.on(...)` casters.
@@ -523,6 +525,26 @@ Returns ValidResult if and only if provided value is not `Datacaster.absent` (th
523
525
 
524
526
  I18n keys: `error_key`, `'.any'`, `'datacaster.errors.any'`
525
527
 
528
+ #### `attribute(*keys)`
529
+
530
+ Always returns ValidResult. Calls provided method(s) (recursively) on the value and returns their results. `*keys` should be specified in exactly the same manner as in [pick](#pickkeys).
531
+
532
+ ```ruby
533
+ class User
534
+ def login
535
+ "Alex"
536
+ end
537
+ end
538
+
539
+ login = Datacaster.schema { attribute(:login) }
540
+
541
+ # => Datacaster::ValidResult("Alex")
542
+ login.(User.new)
543
+
544
+ # => Datacaster::ValidResult(#<Datacaster.absent>)
545
+ login.("test")
546
+ ```
547
+
526
548
  #### `default(default_value, on: nil)`
527
549
 
528
550
  Always returns ValidResult.
@@ -633,7 +655,7 @@ mapping.(
633
655
 
634
656
  See also `#cast_errors` for [error remapping](#error-remapping-cast_errors).
635
657
 
636
- See also `#pick` for [simpler picking of hash values](#pickkey).
658
+ See also `#pick` for [simpler picking of hash values](#pickkeys).
637
659
 
638
660
  I18n keys:
639
661
 
@@ -686,15 +708,18 @@ Returns ValidResult if and only if base returns ValidResult. Returns base's erro
686
708
 
687
709
  Doesn't transform the value: if base succeeds returns the original value (not the one that base returned).
688
710
 
689
- #### `pick(key)`
711
+ #### `pick(*keys)`
690
712
 
691
713
  Returns ValidResult if and only if the value `#is_a?(Enumerable)`.
692
714
 
693
- Transforms the value to/returns:
715
+ Each argument should be a string, a Symbol, an integer or an array thereof. Each argument plays the role of key(s) to fetch from the value:
716
+
717
+ * If the argument is an Array, value is extracted recursively
718
+ * Otherwise, `value[argument]` is fetched and added to the result (or `Datacaster.absent` if is is impossible to fetch)
719
+
720
+ If only one argument is provided to the `pick`, one fetched value is returned. If several arguments are provided, array is returned wherein each value corresponds to each argument.
694
721
 
695
- * `value[key]` if key is set in the value
696
- * `nil` if `value[key]` is set and is nil
697
- * `Datacaster.absent` if key is not set
722
+ Fetching single key:
698
723
 
699
724
  ```ruby
700
725
  pick_name = Datacaster.schema { pick(:name) }
@@ -705,9 +730,7 @@ pick_name.(last_name: "Johnson") # => Datacaster::ValidResult(#<Datacaster.absen
705
730
  pick_name.("test") # => Datacaster::ErrorResult(["is not Enumerable"])
706
731
  ```
707
732
 
708
- Alternative form could be used: `pick(*keys)`.
709
-
710
- In this case, an array of results is returned, each element in which corresponds to the element in `keys` array (i.e. is an argument of the `pick`) and evaluated in accordance with the above rules.
733
+ Fetching multiple keys:
711
734
 
712
735
  ```ruby
713
736
  pick_name_and_age = Datacaster.schema { pick(:name, :age) }
@@ -718,6 +741,15 @@ pick_name_and_age.(last_name: "Johnson", age: 20) # => Datacaster::ValidResult([
718
741
  pick_name_and_age.("test") # => Datacaster::ErrorResult(["is not Enumerable"])
719
742
  ```
720
743
 
744
+ Fetching deeply nested key:
745
+
746
+ ```ruby
747
+ nested_hash_picker = Datacaster.schema { pick([:user, :age]) }
748
+
749
+ nested_hash_picker.(user: { age: 21 }) # => Datacaster::ValidResult(21)
750
+ nested_hash_picker.(user: { name: "Alex" }) # => Datacaster::ValidResult(#<Datacaster.absent>)
751
+ ```
752
+
721
753
  I18n keys:
722
754
 
723
755
  * not a Enumerable – `'.must_be'`, `'datacaster.errors.must_be'`.
@@ -1322,7 +1354,7 @@ Note: in the "root" scope (immediately inside of `schema { ... }` block) the wor
1322
1354
 
1323
1355
  ### Mapping hashes: `transform_to_hash`
1324
1356
 
1325
- One common task in processing compound data structures is to map one set of hash keys to another set. That's where `transform_to_hash` type comes to play (see also [`pick`](#pickkey) and [`remove`](#remove)).
1357
+ One common task in processing compound data structures is to map one set of hash keys to another set. That's where `transform_to_hash` type comes to play (see also [`pick`](#pickkeys) and [`remove`](#remove)).
1326
1358
 
1327
1359
  ```ruby
1328
1360
  city_with_distance =
@@ -7,17 +7,14 @@ module Datacaster
7
7
  attr_accessor :i18n_module
8
8
 
9
9
  def add_predefined_caster(name, definition)
10
- caster =
11
- case definition
12
- when Proc
13
- Datacaster.partial_schema(&definition)
14
- when Base
15
- definition
16
- else
17
- raise ArgumentError.new("Expected Datacaster defintion lambda or Datacaster instance")
18
- end
19
-
20
- Predefined.define_method(name.to_sym) { caster }
10
+ case definition
11
+ when Proc
12
+ Predefined.define_method(name.to_sym, &definition)
13
+ when Base
14
+ Predefined.define_method(name.to_sym) { definition }
15
+ else
16
+ raise ArgumentError.new("Expected Datacaster defintion lambda or Datacaster instance")
17
+ end
21
18
  end
22
19
 
23
20
  def i18n_t
@@ -54,15 +54,15 @@ module Datacaster
54
54
  end
55
55
 
56
56
  def schema(base)
57
- ContextNodes::StructureCleaner.new(base, strategy: :fail)
57
+ ContextNodes::StructureCleaner.new(base, :fail)
58
58
  end
59
59
 
60
60
  def choosy_schema(base)
61
- ContextNodes::StructureCleaner.new(base, strategy: :remove)
61
+ ContextNodes::StructureCleaner.new(base, :remove)
62
62
  end
63
63
 
64
64
  def partial_schema(base)
65
- ContextNodes::StructureCleaner.new(base, strategy: :pass)
65
+ ContextNodes::StructureCleaner.new(base, :pass)
66
66
  end
67
67
 
68
68
  # 'Meta' types
@@ -89,6 +89,26 @@ module Datacaster
89
89
  check { |x| x != Datacaster.absent }.i18n_key(*error_keys)
90
90
  end
91
91
 
92
+ def attribute(*keys)
93
+ if keys.empty? || keys.any? { |k| !Datacaster::Utils.pickable?(k) }
94
+ raise RuntimeError, "each argument should be String, Symbol, Integer or an array thereof", caller
95
+ end
96
+
97
+ transform do |input|
98
+ result =
99
+ keys.map do |key|
100
+ Array(key).reduce(input) do |result, k|
101
+ if result.respond_to?(k)
102
+ result.public_send(k)
103
+ else
104
+ break Datacaster.absent
105
+ end
106
+ end
107
+ end
108
+ keys.length == 1 ? result.first : result
109
+ end
110
+ end
111
+
92
112
  def default(value, on: nil)
93
113
  transform do |x|
94
114
  if x == Datacaster.absent ||
@@ -100,46 +120,108 @@ module Datacaster
100
120
  end
101
121
  end
102
122
 
103
- def transform_to_value(value)
104
- transform { value }
123
+ def merge_message_keys(*keys)
124
+ MessageKeysMerger.new(keys)
125
+ end
126
+
127
+ def must_be(klass, error_key = nil)
128
+ error_keys = ['.must_be', 'datacaster.errors.must_be']
129
+ error_keys.unshift(error_key) if error_key
130
+ check { |x| x.is_a?(klass) }.i18n_key(*error_keys, reference: klass.name)
131
+ end
132
+
133
+ def optional(base, on: nil)
134
+ return absent | base if on == nil
135
+ cast do |x|
136
+ if x == Datacaster.absent ||
137
+ (!on.nil? && x.respond_to?(on) && x.public_send(on))
138
+ Datacaster.ValidResult(Datacaster.absent)
139
+ else
140
+ base.(x)
141
+ end
142
+ end
105
143
  end
106
144
 
107
- # min_amount: has_relation(:min_amount, :>, :max_amount)
145
+ def pass
146
+ transform(&:itself)
147
+ end
148
+
149
+ def pass_if(base)
150
+ ContextNodes::PassIf.new(base)
151
+ end
152
+
153
+ def pick(*keys)
154
+ if keys.empty? || keys.any? { |k| !Datacaster::Utils.pickable?(k) }
155
+ raise RuntimeError, "each argument should be String, Symbol, Integer or an array thereof", caller
156
+ end
157
+
158
+ retrieve_key = -> (from, key) do
159
+ if from.respond_to?(:key?) && !from.key?(key)
160
+ Datacaster.absent
161
+ elsif from.respond_to?(:length) && key.is_a?(Integer) && key > 0 && key >= from.length
162
+ Datacaster.absent
163
+ elsif !from.respond_to?(:[])
164
+ Datacaster.absent
165
+ else
166
+ from[key]
167
+ end
168
+ end
169
+
170
+ must_be(Enumerable) & transform { |input|
171
+ result =
172
+ keys.map do |key|
173
+ Array(key).reduce(input) do |result, k|
174
+ result = retrieve_key.(result, k)
175
+ break result if result == Datacaster.absent
176
+ result
177
+ end
178
+ end
179
+ keys.length == 1 ? result.first : result
180
+ }
181
+ end
108
182
 
109
183
  def relate(left, op, right, error_key: nil)
110
184
  error_keys = ['.relate', 'datacaster.errors.relate']
111
185
  additional_vars = {}
112
186
 
187
+ left_caster = left
188
+ if Datacaster::Utils.pickable?(left)
189
+ left_caster = pick(left)
190
+ elsif !Datacaster.instance?(left)
191
+ raise RuntimeError, "provide String, Symbol, Integer or array thereof instead of #{left.inspect}", caller
192
+ end
193
+
194
+ right_caster = right
195
+ if Datacaster::Utils.pickable?(right)
196
+ right_caster = pick(right)
197
+ elsif !Datacaster.instance?(right)
198
+ raise RuntimeError, "provide String, Symbol, Integer or array thereof instead of #{right.inspect}", caller
199
+ end
200
+
201
+ op_caster = op
202
+ if op.is_a?(String) || op.is_a?(Symbol)
203
+ op_caster = check { |(l, r)| l.respond_to?(op) && l.public_send(op, r) }
204
+ elsif !Datacaster.instance?(left)
205
+ raise RuntimeError, "provide String or Symbol instead of #{op.inspect}", caller
206
+ end
207
+
113
208
  {left: left, op: op, right: right}.each do |k, v|
114
209
  if [String, Symbol, Integer].any? { |c| v.is_a?(c) }
115
210
  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
211
  end
119
212
  end
120
213
 
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) }
214
+ if additional_vars.length == 3
126
215
  error_keys.unshift(".#{left}.#{op}.#{right}")
127
216
  end
128
217
  error_keys.unshift(error_key) if error_key
129
218
 
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
219
  cast do |value|
138
- left_result = left.(value)
220
+ left_result = left_caster.(value)
139
221
  next left_result unless left_result.valid?
140
222
  i18n_var!(:left, left_result.value) unless additional_vars.key?(:left)
141
223
 
142
- right_result = right.(value)
224
+ right_result = right_caster.(value)
143
225
  next right_result unless right_result.valid?
144
226
  i18n_var!(:right, right_result.value) unless additional_vars.key?(:right)
145
227
 
@@ -154,71 +236,21 @@ module Datacaster
154
236
  transform { Datacaster.absent }
155
237
  end
156
238
 
157
- def pass
158
- transform(&:itself)
159
- end
160
-
161
- def switch(*base, **on_clauses)
162
- switch =
163
- if base.length == 0
164
- SwitchNode.new
165
- else
166
- SwitchNode.new(base)
167
- end
168
- on_clauses.reduce(switch) do |result, (k, v)|
169
- result.on(k, v)
170
- end
171
- end
172
-
173
- def pass_if(base)
174
- ContextNodes::PassIf.new(base)
175
- end
176
-
177
- def pick(*keys, strict: false)
178
- raise RuntimeError.new("provide keys to pick, e.g. pick(:key)") if keys.empty?
179
-
180
- must_be(Enumerable) & transform { |value|
181
- result =
182
- keys.map do |key|
183
- if value.respond_to?(:key?) && !value.key?(key)
184
- Datacaster.absent
185
- elsif value.respond_to?(:length) && key.is_a?(Integer) && key > 0 && key >= value.length
186
- Datacaster.absent
187
- else
188
- value[key]
189
- end
190
- end
191
-
192
- keys.length == 1 ? result.first : result
193
- }
194
- end
195
-
196
- def merge_message_keys(*keys)
197
- MessageKeysMerger.new(keys)
198
- end
199
-
200
239
  def responds_to(method, error_key = nil)
201
240
  error_keys = ['.responds_to', 'datacaster.errors.responds_to']
202
241
  error_keys.unshift(error_key) if error_key
203
242
  check { |x| x.respond_to?(method) }.i18n_key(*error_keys, reference: method.to_s)
204
243
  end
205
244
 
206
- def must_be(klass, error_key = nil)
207
- error_keys = ['.must_be', 'datacaster.errors.must_be']
208
- error_keys.unshift(error_key) if error_key
209
- check { |x| x.is_a?(klass) }.i18n_key(*error_keys, reference: klass.name)
245
+ def switch(base = nil, **on_clauses)
246
+ switch = SwitchNode.new(base)
247
+ on_clauses.reduce(switch) do |result, (k, v)|
248
+ result.on(k, v)
249
+ end
210
250
  end
211
251
 
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
252
+ def transform_to_value(value)
253
+ transform { value }
222
254
  end
223
255
 
224
256
  # Strict types
@@ -1,16 +1,14 @@
1
1
  module Datacaster
2
2
  class SwitchNode < Base
3
3
  def initialize(base = nil, on_casters = [], else_caster = nil)
4
- base = base[0] if base.is_a?(Array) && base.length == 1
4
+ @base = base
5
5
 
6
- case base
7
- when nil
8
- @base = nil
9
- when Datacaster::Base
10
- @base = base
11
- when String, Symbol, Array
12
- @base = Datacaster::Predefined.pick(base)
13
- else
6
+ if Datacaster::Utils.pickable?(@base)
7
+ @base = Datacaster::Predefined.pick(@base)
8
+ end
9
+
10
+ if !@base.nil? && !Datacaster.instance?(@base)
11
+ puts @base.inspect
14
12
  raise RuntimeError, "provide a Datacaster::Base instance, a hash key, or an array of keys to switch(...) caster", caller
15
13
  end
16
14
 
@@ -18,11 +16,18 @@ module Datacaster
18
16
  @else = else_caster
19
17
  end
20
18
 
21
- def on(caster_or_value, clause)
19
+ def on(caster_or_value, clause, strict: false)
22
20
  caster =
23
21
  case caster_or_value
24
22
  when Datacaster::Base
25
23
  caster_or_value
24
+ when String, Symbol
25
+ if strict
26
+ Datacaster::Predefined.compare(caster_or_value)
27
+ else
28
+ Datacaster::Predefined.compare(caster_or_value.to_s) |
29
+ Datacaster::Predefined.compare(caster_or_value.to_sym)
30
+ end
26
31
  else
27
32
  Datacaster::Predefined.compare(caster_or_value)
28
33
  end
@@ -30,5 +30,11 @@ module Datacaster
30
30
 
31
31
  result
32
32
  end
33
+
34
+ def pickable?(value)
35
+ is_literal = ->(v) { [String, Symbol, Integer].any? { |c| v.is_a?(c) } }
36
+ is_literal.(value) ||
37
+ value.is_a?(Array) && !value.empty? && value.all? { |v| is_literal.(v) }
38
+ end
33
39
  end
34
40
  end
@@ -1,3 +1,3 @@
1
1
  module Datacaster
2
- VERSION = "3.1.2"
2
+ VERSION = "3.1.5"
3
3
  end
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.2
4
+ version: 3.1.5
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-05 00:00:00.000000000 Z
11
+ date: 2023-10-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activemodel