datacaster 3.1.2 → 3.1.3

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