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 +4 -4
- data/README.md +42 -11
- data/lib/datacaster/predefined.rb +108 -76
- data/lib/datacaster/switch_node.rb +7 -9
- data/lib/datacaster/utils.rb +6 -0
- data/lib/datacaster/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d7cd7191206c373ab38c5516505bf38cbbdc76a7b7c433f976498accda34b054
|
4
|
+
data.tar.gz: 0443edf8c8f494caeacfb55ba902cce19210a53cf9f726e8306f7e487de58fd8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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(
|
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](#
|
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(
|
710
|
+
#### `pick(*keys)`
|
690
711
|
|
691
712
|
Returns ValidResult if and only if the value `#is_a?(Enumerable)`.
|
692
713
|
|
693
|
-
|
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
|
-
|
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
|
-
|
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`](#
|
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
|
104
|
-
|
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
|
-
|
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
|
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 =
|
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 =
|
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
|
207
|
-
|
208
|
-
|
209
|
-
|
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
|
213
|
-
|
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
|
4
|
+
@base = base
|
5
5
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
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
|
|
data/lib/datacaster/utils.rb
CHANGED
@@ -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
|
data/lib/datacaster/version.rb
CHANGED
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.
|
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-
|
11
|
+
date: 2023-10-06 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activemodel
|