datacaster 3.1.2 → 3.1.3

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: d7cd7191206c373ab38c5516505bf38cbbdc76a7b7c433f976498accda34b054
4
+ data.tar.gz: 0443edf8c8f494caeacfb55ba902cce19210a53cf9f726e8306f7e487de58fd8
5
5
  SHA512:
6
- metadata.gz: 2a3573aac6a44ef0a5849da04325c9089119ad529cc6f6384d18bb6dda97f66358081c27d1fc260d95f7adaec5937bda7769cd9207ea9be6b01f2547f4425659
7
- data.tar.gz: '04765854fbe156b3418728d4d2392b78ce0837ead8e1e72b76a51d55e6227bb68eebf8a98e672c924205bbe731610f21c72a1be355c51c2f014886ab160efdb7'
6
+ metadata.gz: 2f00d6cd8c2aae72cba3723dbea6b1c6cb9d760309e9fefa7897e5da691123459496348045849f47700f241df668e505120ba1d2e2f54c93a033e72b222dc3b9
7
+ data.tar.gz: 15c5d76c7cc7a16be05890bec3bcf549c0a5d103e9ba8bb42e2743c834e874bc1296fa5daa3af4feff93107d5acd8db2bd3b2da103f1c29aca1a169b86883c24
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)
@@ -523,6 +524,26 @@ Returns ValidResult if and only if provided value is not `Datacaster.absent` (th
523
524
 
524
525
  I18n keys: `error_key`, `'.any'`, `'datacaster.errors.any'`
525
526
 
527
+ #### `attribute(*keys)`
528
+
529
+ 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).
530
+
531
+ ```ruby
532
+ class User
533
+ def login
534
+ "Alex"
535
+ end
536
+ end
537
+
538
+ login = Datacaster.schema { attribute(:login) }
539
+
540
+ # => Datacaster::ValidResult("Alex")
541
+ login.(User.new)
542
+
543
+ # => Datacaster::ValidResult(#<Datacaster.absent>)
544
+ login.("test")
545
+ ```
546
+
526
547
  #### `default(default_value, on: nil)`
527
548
 
528
549
  Always returns ValidResult.
@@ -633,7 +654,7 @@ mapping.(
633
654
 
634
655
  See also `#cast_errors` for [error remapping](#error-remapping-cast_errors).
635
656
 
636
- See also `#pick` for [simpler picking of hash values](#pickkey).
657
+ See also `#pick` for [simpler picking of hash values](#pickkeys).
637
658
 
638
659
  I18n keys:
639
660
 
@@ -686,15 +707,18 @@ Returns ValidResult if and only if base returns ValidResult. Returns base's erro
686
707
 
687
708
  Doesn't transform the value: if base succeeds returns the original value (not the one that base returned).
688
709
 
689
- #### `pick(key)`
710
+ #### `pick(*keys)`
690
711
 
691
712
  Returns ValidResult if and only if the value `#is_a?(Enumerable)`.
692
713
 
693
- Transforms the value to/returns:
714
+ 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:
715
+
716
+ * If the argument is an Array, value is extracted recursively
717
+ * Otherwise, `value[argument]` is fetched and added to the result (or `Datacaster.absent` if is is impossible to fetch)
718
+
719
+ 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
720
 
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
721
+ Fetching single key:
698
722
 
699
723
  ```ruby
700
724
  pick_name = Datacaster.schema { pick(:name) }
@@ -705,9 +729,7 @@ pick_name.(last_name: "Johnson") # => Datacaster::ValidResult(#<Datacaster.absen
705
729
  pick_name.("test") # => Datacaster::ErrorResult(["is not Enumerable"])
706
730
  ```
707
731
 
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.
732
+ Fetching multiple keys:
711
733
 
712
734
  ```ruby
713
735
  pick_name_and_age = Datacaster.schema { pick(:name, :age) }
@@ -718,6 +740,15 @@ pick_name_and_age.(last_name: "Johnson", age: 20) # => Datacaster::ValidResult([
718
740
  pick_name_and_age.("test") # => Datacaster::ErrorResult(["is not Enumerable"])
719
741
  ```
720
742
 
743
+ Fetching deeply nested key:
744
+
745
+ ```ruby
746
+ nested_hash_picker = Datacaster.schema { pick([:user, :age]) }
747
+
748
+ nested_hash_picker.(user: { age: 21 }) # => Datacaster::ValidResult(21)
749
+ nested_hash_picker.(user: { name: "Alex" }) # => Datacaster::ValidResult(#<Datacaster.absent>)
750
+ ```
751
+
721
752
  I18n keys:
722
753
 
723
754
  * not a Enumerable – `'.must_be'`, `'datacaster.errors.must_be'`.
@@ -1322,7 +1353,7 @@ Note: in the "root" scope (immediately inside of `schema { ... }` block) the wor
1322
1353
 
1323
1354
  ### Mapping hashes: `transform_to_hash`
1324
1355
 
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)).
1356
+ 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
1357
 
1327
1358
  ```ruby
1328
1359
  city_with_distance =
@@ -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
 
@@ -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.3"
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.3
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-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activemodel