datacaster 0.9.1 → 2.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/rspec.yml +17 -0
- data/.gitignore +1 -0
- data/.gitlab-ci.yml +8 -0
- data/.rspec +0 -0
- data/.travis.yml +0 -0
- data/Gemfile +0 -0
- data/LICENSE.txt +0 -0
- data/README.md +298 -10
- data/Rakefile +0 -0
- data/bin/setup +0 -0
- data/datacaster.gemspec +0 -0
- data/lib/datacaster/absent.rb +0 -0
- data/lib/datacaster/and_node.rb +1 -2
- data/lib/datacaster/and_with_error_aggregation_node.rb +1 -2
- data/lib/datacaster/array_schema.rb +2 -2
- data/lib/datacaster/base.rb +33 -2
- data/lib/datacaster/caster.rb +1 -1
- data/lib/datacaster/checker.rb +1 -1
- data/lib/datacaster/comparator.rb +1 -1
- data/lib/datacaster/config.rb +19 -0
- data/lib/datacaster/{runner_context.rb → definition_context.rb} +5 -6
- data/lib/datacaster/hash_mapper.rb +30 -14
- data/lib/datacaster/hash_schema.rb +1 -2
- data/lib/datacaster/message_keys_merger.rb +98 -0
- data/lib/datacaster/or_node.rb +1 -2
- data/lib/datacaster/predefined.rb +5 -0
- data/lib/datacaster/result.rb +0 -0
- data/lib/datacaster/terminator.rb +72 -51
- data/lib/datacaster/then_node.rb +2 -3
- data/lib/datacaster/transformer.rb +1 -1
- data/lib/datacaster/trier.rb +1 -1
- data/lib/datacaster/validator.rb +1 -1
- data/lib/datacaster/version.rb +1 -1
- data/lib/datacaster.rb +29 -17
- metadata +8 -5
- data/Gemfile.lock +0 -59
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2d6493682ec13481f878adb35c36562ac7f38dd9fa761a7447d6f05f2812c98c
|
4
|
+
data.tar.gz: 32ca64152333af7cf9e9125ddeb01783f06249e2949dfdd139ca6bcb12fe4790
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8f354a530cb8371fe744b851cfd81714ffbac336ac6df79c09fe6e75ca14ccfff77a30efebf84205495f1262ec860c2c8a415734f945af256bb6f84698d52003
|
7
|
+
data.tar.gz: 3232aeaa2d583530e3402d61fb321e3f99f0333a0a676defb9e2ae3a8f42e1de358f169b844924737d764514787e498286e6ff7271dcbad8432347e4434670ad
|
@@ -0,0 +1,17 @@
|
|
1
|
+
name: Rspec
|
2
|
+
on: [push, pull_request]
|
3
|
+
jobs:
|
4
|
+
test:
|
5
|
+
runs-on: ubuntu-latest
|
6
|
+
strategy:
|
7
|
+
matrix:
|
8
|
+
ruby-version: ['3.1', '2.7']
|
9
|
+
steps:
|
10
|
+
- uses: actions/checkout@v3
|
11
|
+
- uses: ruby/setup-ruby@v1
|
12
|
+
with:
|
13
|
+
ruby-version: ${{ matrix.ruby }}
|
14
|
+
- name: Install dependencies
|
15
|
+
run: bundle install
|
16
|
+
- name: Run tests
|
17
|
+
run: bundle exec rspec
|
data/.gitignore
CHANGED
data/.gitlab-ci.yml
ADDED
data/.rspec
CHANGED
File without changes
|
data/.travis.yml
CHANGED
File without changes
|
data/Gemfile
CHANGED
File without changes
|
data/LICENSE.txt
CHANGED
File without changes
|
data/README.md
CHANGED
@@ -4,6 +4,70 @@ This gem provides run-time type checking and mapping of composite data structure
|
|
4
4
|
|
5
5
|
Its main use is in the validation and preliminary transformation of API params requests.
|
6
6
|
|
7
|
+
|
8
|
+
# Table of contents
|
9
|
+
|
10
|
+
- [Installing](#installing)
|
11
|
+
- [Why not ...](#why-not-)
|
12
|
+
- [Basics](#basics)
|
13
|
+
- [Conveyor belt](#conveyor-belt)
|
14
|
+
- [Result value](#result-value)
|
15
|
+
- [Hash schema](#hash-schema)
|
16
|
+
- [Logical operators](#logical-operators)
|
17
|
+
- [*AND operator*:](#and-operator)
|
18
|
+
- [*OR operator*:](#or-operator)
|
19
|
+
- [*IF... THEN... ELSE operator*:](#if-then-else-operator)
|
20
|
+
- [Built-in types](#built-in-types)
|
21
|
+
- [Basic types](#basic-types)
|
22
|
+
- [`string`](#string)
|
23
|
+
- [`integer`](#integer)
|
24
|
+
- [`float`](#float)
|
25
|
+
- [`decimal([digits = 8])`](#decimaldigits--8)
|
26
|
+
- [`array`](#array)
|
27
|
+
- [`hash_value`](#hash_value)
|
28
|
+
- [Convenience types](#convenience-types)
|
29
|
+
- [`non_empty_string`](#non_empty_string)
|
30
|
+
- [`hash_with_symbolized_keys`](#hash_with_symbolized_keys)
|
31
|
+
- [`integer32`](#integer32)
|
32
|
+
- [Special types](#special-types)
|
33
|
+
- [`absent`](#absent)
|
34
|
+
- [`any`](#any)
|
35
|
+
- [`transform_to_value(value)`](#transform_to_valuevalue)
|
36
|
+
- [`remove`](#remove)
|
37
|
+
- [`pass`](#pass)
|
38
|
+
- [`responds_to(method)`](#responds_tomethod)
|
39
|
+
- [`must_be(klass)`](#must_beklass)
|
40
|
+
- [`optional(base)`](#optionalbase)
|
41
|
+
- [`pick(key)`](#pickkey)
|
42
|
+
- [`merge_message_keys(*keys)`](#merge_message_keyskeys)
|
43
|
+
- ["Web-form" types](#web-form-types)
|
44
|
+
- [`to_integer`](#to_integer)
|
45
|
+
- [`to_float`](#to_float)
|
46
|
+
- [`to_boolean`](#to_boolean)
|
47
|
+
- [`iso8601`](#iso8601)
|
48
|
+
- [`optional_param(base)`](#optional_parambase)
|
49
|
+
- [Custom and fundamental types](#custom-and-fundamental-types)
|
50
|
+
- [`cast(name = 'Anonymous') { |value| ... }`](#castname--anonymous--value--)
|
51
|
+
- [`check(name = 'Anonymous', error = 'is invalid') { |value| ... }`](#checkname--anonymous-error--is-invalid--value--)
|
52
|
+
- [`try(name = 'Anonymous', error = 'is invalid', catched_exception:) { |value| ... }`](#tryname--anonymous-error--is-invalid-catched_exception--value--)
|
53
|
+
- [`validate(active_model_validations, name = 'Anonymous')`](#validateactive_model_validations-name--anonymous)
|
54
|
+
- [`compare(reference_value, name = 'Anonymous', error = nil)`](#comparereference_value-name--anonymous-error--nil)
|
55
|
+
- [`transform(name = 'Anonymous') { |value| ... }`](#transformname--anonymous--value--)
|
56
|
+
- [`transform_if_present(name = 'Anonymous') { |value| ... }`](#transform_if_presentname--anonymous--value--)
|
57
|
+
- [Passing additional context to schemas](#passing-additional-context-to-schemas)
|
58
|
+
- [Array schemas](#array-schemas)
|
59
|
+
- [Hash schemas](#hash-schemas)
|
60
|
+
- [Absent is not nil](#absent-is-not-nil)
|
61
|
+
- [Schema vs Partial schema](#schema-vs-partial-schema)
|
62
|
+
- [AND with error aggregation (`*`)](#and-with-error-aggregation-)
|
63
|
+
- [Shortcut nested definitions](#shortcut-nested-definitions)
|
64
|
+
- [Mapping hashes: `transform_to_hash`](#mapping-hashes-transform_to_hash)
|
65
|
+
- [Error remapping](#error-remapping)
|
66
|
+
- [Registering custom 'predefined' types](#registering-custom-predefined-types)
|
67
|
+
- [Contributing](#contributing)
|
68
|
+
- [Ideas/TODO](#ideastodo)
|
69
|
+
- [License](#license)
|
70
|
+
|
7
71
|
## Installing
|
8
72
|
|
9
73
|
Add to your Gemfile:
|
@@ -50,7 +114,7 @@ validator.(1).value # nil
|
|
50
114
|
validator.(1).errors # ["must be string"]
|
51
115
|
```
|
52
116
|
|
53
|
-
Datacaster instances are created with a call to `Datacaster.schema { ... }` or `Datacaster.
|
117
|
+
Datacaster instances are created with a call to `Datacaster.schema { ... }`, `Datacaster.partial_schema { ... }` or `Datacaster.choosy_schema { ... }` (described later in this file).
|
54
118
|
|
55
119
|
Datacaster validators' results could be converted to [dry result monad](https://dry-rb.org/gems/dry-monads/1.0/result/):
|
56
120
|
|
@@ -103,9 +167,9 @@ Validating hashes is the main case scenario for datacaster. Several specific con
|
|
103
167
|
Let's assume we want to validate that a hash (which represents data about a person):
|
104
168
|
|
105
169
|
a) is, in fact, a Hash;
|
106
|
-
|
107
|
-
|
108
|
-
|
170
|
+
b) has exactly 2 keys, `name` and `salary`,
|
171
|
+
c) key 'name' is a string,
|
172
|
+
d) key 'salary' is an integer:
|
109
173
|
|
110
174
|
```ruby
|
111
175
|
person_validator =
|
@@ -135,7 +199,7 @@ person_validator.(name: "John Smith", salary: 100_000, title: "developer")
|
|
135
199
|
# => Datacaster::ErrorResult({:title=>["must be absent"]})
|
136
200
|
```
|
137
201
|
|
138
|
-
`Datacaster.schema` definitions don't permit, as you likely noticed from the example above, extra fields in the hash. In fact, `Datacaster.schema` automatically adds special built-in validator, called `Datacaster::Terminator`, at the end of your validation chain, which function is to ensure that all hash keys had been validated.
|
202
|
+
`Datacaster.schema` definitions don't permit, as you likely noticed from the example above, extra fields in the hash. In fact, `Datacaster.schema` automatically adds special built-in validator, called `Datacaster::Terminator::Raising`, at the end of your validation chain, which function is to ensure that all hash keys had been validated.
|
139
203
|
|
140
204
|
If you want to permit your hashes to contain extra fields, use `Datacaster.partial_schema` (it's the only difference between `.schema` and `.partial_schema`):
|
141
205
|
|
@@ -152,6 +216,21 @@ person_with_extra_keys_validator.(name: "John Smith", salary: 100_000, title: "d
|
|
152
216
|
# => Datacaster::ValidResult({:name=>"John Smith", :salary=>100000, :title=>"developer"})
|
153
217
|
```
|
154
218
|
|
219
|
+
Also if you want to delete extra fields, use `Datacaster.choosy_schema`:
|
220
|
+
|
221
|
+
```ruby
|
222
|
+
person_with_extra_keys_validator =
|
223
|
+
Datacaster.choosy_schema do
|
224
|
+
hash_schema(
|
225
|
+
name: string,
|
226
|
+
salary: integer
|
227
|
+
)
|
228
|
+
end
|
229
|
+
|
230
|
+
person_with_extra_keys_validator.(name: "John Smith", salary: 100_000, age: 18)
|
231
|
+
# => Datacaster::ValidResult({:name=>"John Smith", :salary=>100000})
|
232
|
+
```
|
233
|
+
|
155
234
|
Datacaster 'hash schema' makes strict difference between absent and nil values, allows to use shortcuts for defining nested schemas (with no limitation on the level of nesting), and has convinient 'AND with error aggregation' (`*`, same symbol as in numbers multiplication) for joining validation errors of multiple failures. See below in the corresponding sections.
|
156
235
|
|
157
236
|
### Logical operators
|
@@ -177,7 +256,7 @@ even_number.(2)
|
|
177
256
|
even_number.(3)
|
178
257
|
# => Datacaster::ErrorResult(["is invalid"])
|
179
258
|
even_number.("test")
|
180
|
-
# =>
|
259
|
+
# => Datacaster::ErrorResult(["must be integer"])
|
181
260
|
```
|
182
261
|
|
183
262
|
If left-hand validation of AND operator passes, *its result* (not the original value) is passed to the right-hand validation. See below in this file section on transformations where this might be relevant.
|
@@ -200,7 +279,7 @@ Notice that OR operator, if left-hand validation fails, passes the original valu
|
|
200
279
|
|
201
280
|
Let's suppose we want to validate that incoming hash is either 'person' or 'entity', where
|
202
281
|
|
203
|
-
- 'person' is a hash with 3 keys (kind: `:person`, name: string, salary: integer),
|
282
|
+
- 'person' is a hash with 3 keys (kind: `:person`, name: string, salary: integer),
|
204
283
|
- 'entity' is a hash with 4 keys (kind: `:entity`, title: string, form: string, revenue: integer).
|
205
284
|
|
206
285
|
```ruby
|
@@ -325,7 +404,7 @@ max_concurrent_connections = Datacaster.schema { compare(nil).then(transform_to_
|
|
325
404
|
|
326
405
|
max_concurrent_connections.(9) # => Datacaster::ValidResult(9)
|
327
406
|
max_concurrent_connections.("9") # => Datacaster::ErrorResult(["must be integer"])
|
328
|
-
max_concurrent_connections.(nil)
|
407
|
+
max_concurrent_connections.(nil) # => Datacaster::ValidResult(5)
|
329
408
|
```
|
330
409
|
|
331
410
|
#### `remove`
|
@@ -398,6 +477,101 @@ pick_name_and_age.(last_name: "Johnson", age: 20) # => Datacaster::ValidResult([
|
|
398
477
|
pick_name_and_age.("test") # => Datacaster::ErrorResult(["must be Enumerable"])
|
399
478
|
```
|
400
479
|
|
480
|
+
#### `merge_message_keys(*keys)`
|
481
|
+
|
482
|
+
Returns ValidResult only if value `#is_a?(Hash)`.
|
483
|
+
|
484
|
+
Maps incoming hash to Datacaster styled messages.
|
485
|
+
|
486
|
+
```ruby
|
487
|
+
mapper =
|
488
|
+
Datacaster.schema do
|
489
|
+
merge_message_keys(:a, :b)
|
490
|
+
end
|
491
|
+
|
492
|
+
mapper.(a: "1", b: "2") # => Datacaster::ValidResult(["1", "2"])
|
493
|
+
```
|
494
|
+
|
495
|
+
Arrays are merged. Merging `["1", "2"]` and `["2", "3"]` will produce `["1", "2", "3"]`.
|
496
|
+
|
497
|
+
Hash values are merged recursively (deeply) with one another:
|
498
|
+
|
499
|
+
```ruby
|
500
|
+
mapper = Datacaster.schema do
|
501
|
+
transform_to_hash(
|
502
|
+
resourse: merge_message_keys(:resourse),
|
503
|
+
user: merge_message_keys(:user, :login_params),
|
504
|
+
login_params: remove
|
505
|
+
)
|
506
|
+
end
|
507
|
+
|
508
|
+
mapper.(
|
509
|
+
resourse: "request was rejected",
|
510
|
+
user: {
|
511
|
+
age: "too young", password: "too long"
|
512
|
+
},
|
513
|
+
login_params: {
|
514
|
+
password: "should contain special characters",
|
515
|
+
nickname: "too short"
|
516
|
+
}
|
517
|
+
)
|
518
|
+
# => Datacaster::ValidResult({
|
519
|
+
# :resourse=>["request was rejected"],
|
520
|
+
# :user=>{
|
521
|
+
# :age=>["too young"],
|
522
|
+
# :password=>["too long", "should contain special characters"],
|
523
|
+
# :nickname=>["too short"]
|
524
|
+
# }
|
525
|
+
# })
|
526
|
+
```
|
527
|
+
|
528
|
+
Hash value merges non-Hash value by merging it with `:base` key (added if absent):
|
529
|
+
|
530
|
+
```ruby
|
531
|
+
mapping = Datacaster.schema do
|
532
|
+
transform_to_hash(
|
533
|
+
resourse: merge_message_keys(:resourse),
|
534
|
+
user: merge_message_keys(:user, :user_error),
|
535
|
+
user_error: remove
|
536
|
+
)
|
537
|
+
end
|
538
|
+
|
539
|
+
mapping.(
|
540
|
+
resourse: "request was rejected",
|
541
|
+
user: {age: "too young", nickname: "too long"},
|
542
|
+
user_error: "user is invalid"
|
543
|
+
)
|
544
|
+
# => Datacaster::ValidResult({
|
545
|
+
# :resourse=>["request was rejected"],
|
546
|
+
# :user=>{
|
547
|
+
# :age=>["too young"],
|
548
|
+
# :nickname=>["too long"],
|
549
|
+
# :base=>["user is invalid"]
|
550
|
+
# }
|
551
|
+
# })
|
552
|
+
```
|
553
|
+
|
554
|
+
Hash keys with `nil` and `[]` values are deeply ignored:
|
555
|
+
|
556
|
+
```ruby
|
557
|
+
mapping = Datacaster.schema do
|
558
|
+
transform_to_hash(
|
559
|
+
user: merge_message_keys(:user),
|
560
|
+
)
|
561
|
+
end
|
562
|
+
|
563
|
+
mapping.(
|
564
|
+
user: {
|
565
|
+
age: "too young", nickname: [], user_error: nil
|
566
|
+
}
|
567
|
+
)
|
568
|
+
# => Datacaster::ValidResult({
|
569
|
+
# :user=> {
|
570
|
+
# :age=>["too young"]
|
571
|
+
# }
|
572
|
+
# })
|
573
|
+
```
|
574
|
+
|
401
575
|
### "Web-form" types
|
402
576
|
|
403
577
|
These types are convenient to parse and validate POST forms and decode JSON requests.
|
@@ -572,6 +746,49 @@ city.(name: "Denver", distance: "2.5") # => Datacaster::ValidResult({:name=>"Den
|
|
572
746
|
|
573
747
|
Always returns ValidResult. If the value is `Datacaster.absent` (singleton instance, see below section on hash schemas), then `Datacaster.absent` is returned (block isn't called). Otherwise, works like `transform`.
|
574
748
|
|
749
|
+
### Passing additional context to schemas
|
750
|
+
|
751
|
+
You can pass `context` to schema using `.with_context` method
|
752
|
+
|
753
|
+
```ruby
|
754
|
+
# class User < ApplicationRecord
|
755
|
+
# ...
|
756
|
+
# end
|
757
|
+
#
|
758
|
+
# class Post < ApplicationRecord
|
759
|
+
# belongs_to :user
|
760
|
+
# ...
|
761
|
+
# end
|
762
|
+
|
763
|
+
schema =
|
764
|
+
Datacaster.schema do
|
765
|
+
hash_schema(
|
766
|
+
post_id: to_integer & check { |id| Post.where(id: id, user_id: context.current_user).exists? }
|
767
|
+
)
|
768
|
+
end
|
769
|
+
|
770
|
+
current_user = ...
|
771
|
+
|
772
|
+
schema.with_context(current_user: current_user).(post_id: 15)
|
773
|
+
```
|
774
|
+
|
775
|
+
`context` is an [OpenStruct](https://ruby-doc.org/stdlib-3.1.0/libdoc/ostruct/rdoc/OpenStruct.html) instance which is initialized in `.with_context`
|
776
|
+
|
777
|
+
**Note**
|
778
|
+
|
779
|
+
`context` can be accesed only in types' blocks:
|
780
|
+
```ruby
|
781
|
+
mail_transformer = Datacaster.schema { transform { |v| "#{v}#{context.postfix}" } }
|
782
|
+
|
783
|
+
mail_transformer.with_context(postfix: "@domen.com").("admin")
|
784
|
+
# => #<Datacaster::ValidResult("admin@domen.com")>
|
785
|
+
```
|
786
|
+
It can't be used in schema definition block itself:
|
787
|
+
```ruby
|
788
|
+
Datacaster.schema { context.error }
|
789
|
+
# leads to `NoMethodError`
|
790
|
+
```
|
791
|
+
|
575
792
|
### Array schemas
|
576
793
|
|
577
794
|
To define compound data type, array of 'something', use `array_schema(something)` (or, synonymically, `array_of(something)`). There is no way to define array wherein each element is of different type.
|
@@ -660,7 +877,7 @@ If a) fails, `ErrorResult(["must be hash"])` is returned.
|
|
660
877
|
if b) fails, `ErrorResult(key1 => [errors...], key2 => [errors...])` is returned. Each key of wrapped "error hash" corresponds to the key of validated hash, and each value of "error hash" contains array of errors, returned by the corresponding validator.
|
661
878
|
If b) fulfilled, then and only then validated hash is checked for extra keys. If they are found, `ErrorResult(extra_key_1 => ["must be absent"], ...)` is returned.
|
662
879
|
|
663
|
-
Technically, last part is implemented with special singleton validator, called `#<Datacaster::Terminator>`, which is automatically added to the validation chain (with the use of `&` operator) by `Datacaster.schema` method. Don't be scared if you see it in the output of `#inspect` method of your validators (e.g. in `irb`).
|
880
|
+
Technically, last part is implemented with special singleton validator, called `#<Datacaster::Terminator::Raising>`, which is automatically added to the validation chain (with the use of `&` operator) by `Datacaster.schema` method. Don't be scared if you see it in the output of `#inspect` method of your validators (e.g. in `irb`).
|
664
881
|
|
665
882
|
#### Absent is not nil
|
666
883
|
|
@@ -735,7 +952,7 @@ Sometimes it is necessary to omit that requirement and allow for hash to contain
|
|
735
952
|
|
736
953
|
Let's say we have:
|
737
954
|
|
738
|
-
* 'people' (hashes with `name: string`, `description: string` and `kind: 'person'` fields),
|
955
|
+
* 'people' (hashes with `name: string`, `description: string` and `kind: 'person'` fields),
|
739
956
|
* 'entities' (hash with `title: string`, `description: string` and `kind: 'entity'` fields).
|
740
957
|
|
741
958
|
In other words, we have some polymorphic resource, which type is defined by `kind` field, and which has common fields for all its "sub-kinds" (in this example: `description`), and also fields specific to each "kind" (in database we often model this as [STI](https://api.rubyonrails.org/v6.0.3.2/classes/ActiveRecord/Base.html#class-ActiveRecord::Base-label-Single+table+inheritance)).
|
@@ -967,6 +1184,77 @@ Here is what is happening when `city_with_distance` (from the example above) is
|
|
967
1184
|
|
968
1185
|
Note: because of point e) above we need to explicitly delete `distance_in_meters` key, because otherwise `transform_to_hash` will copy it to the resultant hash without validation. And all non-validated keys at the end of `Datacaster.schema` block (as explained above in section on partial schemas) result in error.
|
969
1186
|
|
1187
|
+
## Error remapping
|
1188
|
+
|
1189
|
+
In some cases it might be useful to remap resulting `Datacaster::ErrorResult`:
|
1190
|
+
|
1191
|
+
```ruby
|
1192
|
+
schema =
|
1193
|
+
Datacaster.schema do
|
1194
|
+
transform = transform_to_hash(
|
1195
|
+
posts: pick(:user_id) & to_integer & transform { |user| Posts.where(user_id: user.id).to_a },
|
1196
|
+
user_id: remove
|
1197
|
+
)
|
1198
|
+
end
|
1199
|
+
|
1200
|
+
schema.(user_id: 'wrong') # => #<Datacaster::ErrorResult({:posts=>["must be integer"]})>
|
1201
|
+
# Instead of #<Datacaster::ErrorResult({:user_id=>["must be integer"]})>
|
1202
|
+
```
|
1203
|
+
|
1204
|
+
`.cast_errors` can be used in such case:
|
1205
|
+
|
1206
|
+
```ruby
|
1207
|
+
schema =
|
1208
|
+
Datacaster.schema do
|
1209
|
+
transform = transform_to_hash(
|
1210
|
+
posts: pick(:user_id) & to_integer & transform { |user| Posts.where(user_id: user.id).to_a },
|
1211
|
+
user_id: remove
|
1212
|
+
)
|
1213
|
+
|
1214
|
+
transform.cast_errors(
|
1215
|
+
transform_to_hash(
|
1216
|
+
user_id: pick(:posts),
|
1217
|
+
posts: remove
|
1218
|
+
)
|
1219
|
+
)
|
1220
|
+
end
|
1221
|
+
|
1222
|
+
schema.(user_id: 'wrong') # => #<Datacaster::ErrorResult({:user_id=>["must be integer"]})>
|
1223
|
+
```
|
1224
|
+
any instance of `Datacaster` can be passed to `.cast_errors`
|
1225
|
+
|
1226
|
+
|
1227
|
+
## Registering custom 'predefined' types
|
1228
|
+
|
1229
|
+
In order to extend `Datacaster` functionality, custom types can be added
|
1230
|
+
|
1231
|
+
There are two ways to add cutsom types to `Datacaster`:
|
1232
|
+
|
1233
|
+
1\. Using lambda definition:
|
1234
|
+
|
1235
|
+
```ruby
|
1236
|
+
Datacaster::Config.add_predefined_caster(:time_string, -> {
|
1237
|
+
string & validate(format: { with: /\A(0[0-9]|1[0-9]|2[0-3]):[03]0\z/ })
|
1238
|
+
})
|
1239
|
+
|
1240
|
+
schema = Datacaster.schema { time_string }
|
1241
|
+
|
1242
|
+
schema.("23:00") # => #<Datacaster::ValidResult("23:00")>
|
1243
|
+
schema.("no_time_string") # => #<Datacaster::ErrorResult(["is invalid"])>
|
1244
|
+
```
|
1245
|
+
|
1246
|
+
2\. Using `Datacaster` instance:
|
1247
|
+
|
1248
|
+
```ruby
|
1249
|
+
css_color = Datacaster.partial_schema { string & validate(format: { with: /\A#(?:\h{3}){1,2}\z/ }) }
|
1250
|
+
Datacaster::Config.add_predefined_caster(:css_color, css_color)
|
1251
|
+
|
1252
|
+
schema = Datacaster.schema { css_color }
|
1253
|
+
|
1254
|
+
schema.("#123456") # => #<Datacaster::ValidResult("#123456")>
|
1255
|
+
schema.("no_css_color") # => #<Datacaster::ErrorResult(["is invalid"])>
|
1256
|
+
```
|
1257
|
+
|
970
1258
|
## Contributing
|
971
1259
|
|
972
1260
|
Fork, create issues and make PRs as usual.
|
data/Rakefile
CHANGED
File without changes
|
data/bin/setup
CHANGED
File without changes
|
data/datacaster.gemspec
CHANGED
File without changes
|
data/lib/datacaster/absent.rb
CHANGED
File without changes
|
data/lib/datacaster/and_node.rb
CHANGED
@@ -7,9 +7,8 @@ module Datacaster
|
|
7
7
|
|
8
8
|
# Works like AndNode, but doesn't stop at first error — in order to aggregate all Failures
|
9
9
|
# Makes sense only for Hash Schemas
|
10
|
-
def
|
10
|
+
def cast(object)
|
11
11
|
object = super(object)
|
12
|
-
|
13
12
|
left_result = @left.(object)
|
14
13
|
|
15
14
|
if left_result.valid?
|
@@ -2,10 +2,10 @@ module Datacaster
|
|
2
2
|
class ArraySchema < Base
|
3
3
|
def initialize(element_caster)
|
4
4
|
# support of shortcut nested validation definitions, e.g. array_schema({a: [integer], b: {c: integer}})
|
5
|
-
@element_caster = shortcut_definition(element_caster)
|
5
|
+
@element_caster = shortcut_definition(element_caster)
|
6
6
|
end
|
7
7
|
|
8
|
-
def
|
8
|
+
def cast(object)
|
9
9
|
object = super(object)
|
10
10
|
checked_schema = object.meta[:checked_schema] || []
|
11
11
|
|
data/lib/datacaster/base.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
require "ostruct"
|
2
|
+
|
1
3
|
module Datacaster
|
2
4
|
class Base
|
3
5
|
def self.merge_errors(left, right)
|
@@ -45,8 +47,33 @@ module Datacaster
|
|
45
47
|
ThenNode.new(self, other)
|
46
48
|
end
|
47
49
|
|
50
|
+
def set_definition_context(definition_context)
|
51
|
+
@definition_context = definition_context
|
52
|
+
end
|
53
|
+
|
54
|
+
def with_context(additional_context)
|
55
|
+
@definition_context.context = OpenStruct.new(additional_context)
|
56
|
+
self
|
57
|
+
end
|
58
|
+
|
48
59
|
def call(object)
|
49
|
-
|
60
|
+
object = cast(object)
|
61
|
+
|
62
|
+
return object if object.valid? || @cast_errors.nil?
|
63
|
+
|
64
|
+
error_cast = @cast_errors.(object.errors)
|
65
|
+
|
66
|
+
raise "#cast_errors must return Datacaster.ValidResult, currently it is #{error_cast.inspect}" unless error_cast.valid?
|
67
|
+
|
68
|
+
Datacaster.ErrorResult(
|
69
|
+
@cast_errors.(object.errors).value,
|
70
|
+
meta: object.meta
|
71
|
+
)
|
72
|
+
end
|
73
|
+
|
74
|
+
def cast_errors(object)
|
75
|
+
@cast_errors = shortcut_definition(object)
|
76
|
+
self
|
50
77
|
end
|
51
78
|
|
52
79
|
def inspect
|
@@ -55,6 +82,10 @@ module Datacaster
|
|
55
82
|
|
56
83
|
private
|
57
84
|
|
85
|
+
def cast(object)
|
86
|
+
Datacaster.ValidResult(object)
|
87
|
+
end
|
88
|
+
|
58
89
|
# Translates hashes like {a: <IntegerChecker>} to <HashSchema {a: <IntegerChecker>}>
|
59
90
|
# and arrays like [<IntegerChecker>] to <ArraySchema <IntegerChecker>>
|
60
91
|
def shortcut_definition(definition)
|
@@ -63,7 +94,7 @@ module Datacaster
|
|
63
94
|
definition
|
64
95
|
when Array
|
65
96
|
if definition.length != 1
|
66
|
-
raise ArgumentError.new("Datacaster:
|
97
|
+
raise ArgumentError.new("Datacaster: shortcut array definitions must have exactly 1 element in the array, e.g. [integer]")
|
67
98
|
end
|
68
99
|
ArraySchema.new(definition.first)
|
69
100
|
when Hash
|
data/lib/datacaster/caster.rb
CHANGED
data/lib/datacaster/checker.rb
CHANGED
@@ -0,0 +1,19 @@
|
|
1
|
+
module Datacaster
|
2
|
+
module Config
|
3
|
+
extend self
|
4
|
+
|
5
|
+
def add_predefined_caster(name, definition)
|
6
|
+
caster =
|
7
|
+
case definition
|
8
|
+
when Proc
|
9
|
+
Datacaster.partial_schema(&definition)
|
10
|
+
when Base
|
11
|
+
definition
|
12
|
+
else
|
13
|
+
raise ArgumentError.new("Expected Datacaster defintion lambda or Datacaster instance")
|
14
|
+
end
|
15
|
+
|
16
|
+
Predefined.define_method(name.to_sym) { caster }
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -2,19 +2,18 @@ require 'bigdecimal'
|
|
2
2
|
require 'date'
|
3
3
|
|
4
4
|
module Datacaster
|
5
|
-
class
|
6
|
-
include Singleton
|
5
|
+
class DefinitionContext
|
7
6
|
include Datacaster::Predefined
|
8
7
|
include Dry::Monads[:result]
|
9
8
|
|
10
|
-
|
9
|
+
attr_accessor :context
|
11
10
|
|
12
|
-
def m(
|
13
|
-
raise
|
11
|
+
def m(_definition)
|
12
|
+
raise "not implemented"
|
14
13
|
end
|
15
14
|
|
16
15
|
def method_missing(m, *args)
|
17
|
-
arg_string = args.empty? ?
|
16
|
+
arg_string = args.empty? ? "" : "(#{args.map(&:inspect).join(', ')})"
|
18
17
|
raise "Datacaster: unknown definition '#{m}#{arg_string}'"
|
19
18
|
end
|
20
19
|
end
|
@@ -1,12 +1,20 @@
|
|
1
1
|
module Datacaster
|
2
2
|
class HashMapper < Base
|
3
3
|
def initialize(fields)
|
4
|
+
keys = fields.keys.flatten
|
5
|
+
if keys.length != keys.uniq.length
|
6
|
+
intersection = keys.select { |k| keys.count(k) > 1 }.uniq.sort
|
7
|
+
raise ArgumentError.new("When using transform_to_hash([:a, :b, :c] => validator), " \
|
8
|
+
"each key should not be mentioned more than once on the left-hand-side. Instead, got these " \
|
9
|
+
"keys mentioned twice or more: #{intersection.inspect}."
|
10
|
+
)
|
11
|
+
end
|
12
|
+
|
4
13
|
@fields = fields
|
5
14
|
end
|
6
15
|
|
7
|
-
def
|
16
|
+
def cast(object)
|
8
17
|
object = super(object)
|
9
|
-
|
10
18
|
# return Datacaster.ErrorResult(["must be hash"]) unless object.value.is_a?(Hash)
|
11
19
|
|
12
20
|
checked_schema = object.meta[:checked_schema].dup || {}
|
@@ -18,24 +26,32 @@ module Datacaster
|
|
18
26
|
new_value = validator.(object)
|
19
27
|
|
20
28
|
# transform_to_hash([:a, :b, :c] => pick(:a, :b, :c) & ...)
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
"
|
26
|
-
|
29
|
+
if key.is_a?(Array)
|
30
|
+
unwrapped = new_value.valid? ? new_value.value : new_value.errors
|
31
|
+
|
32
|
+
if key.length != unwrapped.length
|
33
|
+
raise TypeError.new("When using transform_to_hash([:a, :b, :c] => validator), validator should return Array "\
|
34
|
+
"with number of elements equal to the number of elements in left-hand-side array.\n" \
|
35
|
+
"Got the following (values or errors) instead: #{keys.inspect} => #{values_or_errors.inspect}.")
|
36
|
+
end
|
27
37
|
end
|
28
38
|
|
29
39
|
if new_value.valid?
|
30
|
-
|
31
|
-
|
40
|
+
if key.is_a?(Array)
|
41
|
+
key.zip(new_value.value) do |new_key, new_key_value|
|
42
|
+
result[new_key] = new_key_value
|
43
|
+
checked_schema[new_key] = true
|
44
|
+
end
|
45
|
+
else
|
46
|
+
result[key] = new_value.value
|
32
47
|
checked_schema[key] = true
|
33
48
|
end
|
34
|
-
|
35
|
-
single_returned_schema = new_value.meta[:checked_schema].dup
|
36
|
-
checked_schema[keys.first] = single_returned_schema if keys.length == 1 && single_returned_schema
|
37
49
|
else
|
38
|
-
|
50
|
+
if key.is_a?(Array)
|
51
|
+
errors = self.class.merge_errors(errors, key.zip(new_value.errors).to_h)
|
52
|
+
else
|
53
|
+
errors = self.class.merge_errors(errors, {key => new_value.errors})
|
54
|
+
end
|
39
55
|
end
|
40
56
|
end
|
41
57
|
|
@@ -6,9 +6,8 @@ module Datacaster
|
|
6
6
|
@fields.transform_values! { |validator| shortcut_definition(validator) }
|
7
7
|
end
|
8
8
|
|
9
|
-
def
|
9
|
+
def cast(object)
|
10
10
|
object = super(object)
|
11
|
-
|
12
11
|
return Datacaster.ErrorResult(["must be hash"]) unless object.value.is_a?(Hash)
|
13
12
|
|
14
13
|
checked_schema = object.meta[:checked_schema].dup || {}
|
@@ -0,0 +1,98 @@
|
|
1
|
+
module Datacaster
|
2
|
+
class MessageKeysMerger < Base
|
3
|
+
def initialize(keys)
|
4
|
+
@keys = keys
|
5
|
+
end
|
6
|
+
|
7
|
+
def cast(object)
|
8
|
+
intermediary_result = super(object)
|
9
|
+
object = intermediary_result.value
|
10
|
+
|
11
|
+
return Datacaster.ErrorResult(["must be Hash"]) unless object.is_a?(Hash)
|
12
|
+
|
13
|
+
result = set_initial_value(object)
|
14
|
+
|
15
|
+
@keys.each do |k|
|
16
|
+
result =
|
17
|
+
if need_hash_merger?(object)
|
18
|
+
merge_hash(result, object[k])
|
19
|
+
else
|
20
|
+
merge_array_or_scalar(result, object[k])
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
result = clean(result)
|
25
|
+
|
26
|
+
Datacaster.ValidResult(
|
27
|
+
result.nil? ? Datacaster.absent : result
|
28
|
+
)
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def set_initial_value(object)
|
34
|
+
need_hash_merger?(object) ? {} : []
|
35
|
+
end
|
36
|
+
|
37
|
+
def need_hash_merger?(object)
|
38
|
+
@need_hash_merger =
|
39
|
+
@need_hash_merger.nil? ? @keys.any? { |k| object[k].is_a?(Hash) } : @need_hash_merger
|
40
|
+
end
|
41
|
+
|
42
|
+
def merge_array_or_scalar(unit, merge_with)
|
43
|
+
merge_with = [merge_with] unless merge_with.is_a?(Array)
|
44
|
+
unit = [unit] unless unit.is_a?(Array)
|
45
|
+
|
46
|
+
result = clean(unit | merge_with)
|
47
|
+
result.uniq == [nil] || result == [] ? Datacaster.absent : result
|
48
|
+
end
|
49
|
+
|
50
|
+
def value_or(value, default)
|
51
|
+
value.nil? ? default : value
|
52
|
+
end
|
53
|
+
|
54
|
+
def merge_hash_with_hash(result, merge_with)
|
55
|
+
if merge_with.is_a?(Hash)
|
56
|
+
merge_with.each do |k, v|
|
57
|
+
if merge_with.is_a?(Hash)
|
58
|
+
result = value_or(result, {})
|
59
|
+
result[k] = merge_hash_with_hash(result[k], v)
|
60
|
+
else
|
61
|
+
result[k] = merge_array_or_scalar(value_or(result[k], []), v)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
result
|
66
|
+
else
|
67
|
+
result = merge_array_or_scalar(value_or(result, []), merge_with)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def merge_hash(result, merge_with)
|
72
|
+
if merge_with.is_a?(Hash)
|
73
|
+
merge_with.each do |k, v|
|
74
|
+
result[k] = merge_hash_with_hash(result[k], v)
|
75
|
+
end
|
76
|
+
else
|
77
|
+
result[:base] = merge_array_or_scalar(value_or(result[:base], []), merge_with)
|
78
|
+
end
|
79
|
+
|
80
|
+
result
|
81
|
+
end
|
82
|
+
|
83
|
+
def clean(value)
|
84
|
+
case value
|
85
|
+
when Array
|
86
|
+
value.delete_if do |v|
|
87
|
+
clean(v) if v.is_a?(Hash) || v.is_a?(Array)
|
88
|
+
v == Datacaster.absent
|
89
|
+
end
|
90
|
+
when Hash
|
91
|
+
value.delete_if do |_k, v|
|
92
|
+
clean(v) if v.is_a?(Hash) || v.is_a?(Array)
|
93
|
+
v == Datacaster.absent
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
data/lib/datacaster/or_node.rb
CHANGED
@@ -33,6 +33,7 @@ module Datacaster
|
|
33
33
|
def array_schema(element_caster)
|
34
34
|
ArraySchema.new(element_caster)
|
35
35
|
end
|
36
|
+
alias_method :array_of, :array_schema
|
36
37
|
|
37
38
|
def hash_schema(fields)
|
38
39
|
HashSchema.new(fields)
|
@@ -85,6 +86,10 @@ module Datacaster
|
|
85
86
|
}
|
86
87
|
end
|
87
88
|
|
89
|
+
def merge_message_keys(*keys)
|
90
|
+
MessageKeysMerger.new(keys)
|
91
|
+
end
|
92
|
+
|
88
93
|
def responds_to(method)
|
89
94
|
check('RespondsTo', "must respond to #{method.inspect}") { |x| x.respond_to?(method) }
|
90
95
|
end
|
data/lib/datacaster/result.rb
CHANGED
File without changes
|
@@ -2,75 +2,96 @@ require 'singleton'
|
|
2
2
|
require 'dry-monads'
|
3
3
|
|
4
4
|
module Datacaster
|
5
|
-
class Terminator
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
checked_schema ||= object.meta[:checked_schema]
|
12
|
-
|
13
|
-
case object.value
|
14
|
-
when Array
|
15
|
-
check_array(object.value, checked_schema)
|
16
|
-
when Hash
|
17
|
-
check_hash(object.value, checked_schema)
|
18
|
-
else
|
19
|
-
Datacaster.ValidResult(object.value)
|
5
|
+
class Terminator
|
6
|
+
module TerminatorBase
|
7
|
+
include Dry::Monads[:result]
|
8
|
+
|
9
|
+
def self.included(klass)
|
10
|
+
klass.include Singleton
|
20
11
|
end
|
21
|
-
end
|
22
12
|
|
23
|
-
|
24
|
-
|
25
|
-
|
13
|
+
def cast(object, checked_schema = nil)
|
14
|
+
object = super(object)
|
15
|
+
checked_schema ||= object.meta[:checked_schema]
|
16
|
+
|
17
|
+
case object.value
|
18
|
+
when Array
|
19
|
+
check_array(object.value, checked_schema)
|
20
|
+
when Hash
|
21
|
+
check_hash(object.value, checked_schema)
|
22
|
+
else
|
23
|
+
Datacaster.ValidResult(object.value)
|
24
|
+
end
|
25
|
+
end
|
26
26
|
|
27
|
-
|
27
|
+
def inspect
|
28
|
+
"#<Datacaster::Terminator>"
|
29
|
+
end
|
28
30
|
|
29
|
-
|
30
|
-
return Datacaster.ValidResult(array) unless checked_schema
|
31
|
+
private
|
31
32
|
|
32
|
-
|
33
|
+
def check_array(array, checked_schema)
|
34
|
+
return Datacaster.ValidResult(array) unless checked_schema
|
33
35
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
36
|
+
result = array.zip(checked_schema).map { |x, schema| cast(x, schema) }
|
37
|
+
|
38
|
+
if result.all?(&:valid?)
|
39
|
+
Datacaster.ValidResult(result.map(&:value))
|
40
|
+
else
|
41
|
+
Datacaster.ErrorResult(result.each.with_index.reject { |x, _| x.valid? }.map { |x, i| [i, x.errors] }.to_h)
|
42
|
+
end
|
38
43
|
end
|
39
|
-
end
|
40
44
|
|
41
|
-
|
42
|
-
|
45
|
+
def check_hash(hash, checked_schema)
|
46
|
+
return Datacaster.ValidResult(hash) unless checked_schema
|
43
47
|
|
44
|
-
|
45
|
-
|
48
|
+
errors = {}
|
49
|
+
result = {}
|
46
50
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
+
hash.each do |(k, v)|
|
52
|
+
if v == Datacaster.absent
|
53
|
+
next
|
54
|
+
end
|
51
55
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
+
unless checked_schema.key?(k)
|
57
|
+
errors[k] = ["must be absent"] if is_a?(Raising)
|
58
|
+
next
|
59
|
+
end
|
60
|
+
|
61
|
+
if checked_schema[k] == true
|
62
|
+
result[k] = v
|
63
|
+
next
|
64
|
+
end
|
56
65
|
|
57
|
-
|
58
|
-
|
59
|
-
|
66
|
+
nested_value = cast(v, checked_schema[k])
|
67
|
+
if nested_value.valid?
|
68
|
+
result[k] = nested_value.value
|
69
|
+
else
|
70
|
+
errors[k] = nested_value.errors
|
71
|
+
end
|
60
72
|
end
|
61
73
|
|
62
|
-
|
63
|
-
|
64
|
-
result[k] = nested_value.value
|
74
|
+
if errors.empty?
|
75
|
+
Datacaster.ValidResult(result)
|
65
76
|
else
|
66
|
-
|
77
|
+
Datacaster.ErrorResult(errors)
|
67
78
|
end
|
68
79
|
end
|
80
|
+
end
|
81
|
+
|
82
|
+
class Raising < Base
|
83
|
+
include TerminatorBase
|
84
|
+
|
85
|
+
def inspect
|
86
|
+
"#<Datacaster::Terminator::Raising>"
|
87
|
+
end
|
88
|
+
end
|
69
89
|
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
90
|
+
class Sweeping < Base
|
91
|
+
include TerminatorBase
|
92
|
+
|
93
|
+
def inspect
|
94
|
+
"#<Datacaster::Terminator::Sweeping>"
|
74
95
|
end
|
75
96
|
end
|
76
97
|
end
|
data/lib/datacaster/then_node.rb
CHANGED
@@ -6,19 +6,18 @@ module Datacaster
|
|
6
6
|
end
|
7
7
|
|
8
8
|
def else(else_caster)
|
9
|
-
raise ArgumentError.new(
|
9
|
+
raise ArgumentError.new("Datacaster: double else clause is not permitted") if @else
|
10
10
|
|
11
11
|
@else = else_caster
|
12
12
|
self
|
13
13
|
end
|
14
14
|
|
15
|
-
def
|
15
|
+
def cast(object)
|
16
16
|
unless @else
|
17
17
|
raise ArgumentError.new('Datacaster: use "a & b" instead of "a.then(b)" when there is no else-clause')
|
18
18
|
end
|
19
19
|
|
20
20
|
object = super(object)
|
21
|
-
|
22
21
|
left_result = @left.(object)
|
23
22
|
|
24
23
|
if left_result.valid?
|
data/lib/datacaster/trier.rb
CHANGED
data/lib/datacaster/validator.rb
CHANGED
data/lib/datacaster/version.rb
CHANGED
data/lib/datacaster.rb
CHANGED
@@ -4,15 +4,17 @@ require_relative 'datacaster/version'
|
|
4
4
|
require_relative 'datacaster/absent'
|
5
5
|
require_relative 'datacaster/base'
|
6
6
|
require_relative 'datacaster/predefined'
|
7
|
-
require_relative 'datacaster/
|
7
|
+
require_relative 'datacaster/definition_context'
|
8
8
|
require_relative 'datacaster/terminator'
|
9
|
+
require_relative 'datacaster/config'
|
9
10
|
|
11
|
+
require_relative 'datacaster/array_schema'
|
10
12
|
require_relative 'datacaster/caster'
|
11
13
|
require_relative 'datacaster/checker'
|
12
14
|
require_relative 'datacaster/comparator'
|
13
|
-
require_relative 'datacaster/array_schema'
|
14
|
-
require_relative 'datacaster/hash_schema'
|
15
15
|
require_relative 'datacaster/hash_mapper'
|
16
|
+
require_relative 'datacaster/hash_schema'
|
17
|
+
require_relative 'datacaster/message_keys_merger'
|
16
18
|
require_relative 'datacaster/transformer'
|
17
19
|
require_relative 'datacaster/trier'
|
18
20
|
|
@@ -22,29 +24,39 @@ require_relative 'datacaster/or_node'
|
|
22
24
|
require_relative 'datacaster/then_node'
|
23
25
|
|
24
26
|
module Datacaster
|
25
|
-
|
26
|
-
raise "Expected block" unless block
|
27
|
+
extend self
|
27
28
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
29
|
+
def schema(&block)
|
30
|
+
build_schema(Terminator::Raising.instance, &block)
|
31
|
+
end
|
32
|
+
|
33
|
+
def choosy_schema(&block)
|
34
|
+
build_schema(Terminator::Sweeping.instance, &block)
|
35
|
+
end
|
32
36
|
|
33
|
-
|
37
|
+
def partial_schema(&block)
|
38
|
+
build_schema(nil, &block)
|
34
39
|
end
|
35
40
|
|
36
|
-
def
|
41
|
+
def absent
|
42
|
+
Datacaster::Absent.instance
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def build_schema(terminator, &block)
|
37
48
|
raise "Expected block" unless block
|
38
49
|
|
39
|
-
|
50
|
+
definition_context = DefinitionContext.new
|
51
|
+
|
52
|
+
datacaster = definition_context.instance_exec(&block)
|
53
|
+
|
40
54
|
unless datacaster.is_a?(Base)
|
41
|
-
raise "Datacaster instance should be returned from a block (e.g. result of '
|
55
|
+
raise "Datacaster instance should be returned from a block (e.g. result of 'hash_schema(...)' call)"
|
42
56
|
end
|
43
57
|
|
58
|
+
datacaster = (datacaster & terminator) if terminator
|
59
|
+
datacaster.set_definition_context(definition_context)
|
44
60
|
datacaster
|
45
61
|
end
|
46
|
-
|
47
|
-
def self.absent
|
48
|
-
Datacaster::Absent.instance
|
49
|
-
end
|
50
62
|
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: 0.
|
4
|
+
version: 2.0.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Eugene Zolotarev
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2023-03-01 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activemodel
|
@@ -79,11 +79,12 @@ executables: []
|
|
79
79
|
extensions: []
|
80
80
|
extra_rdoc_files: []
|
81
81
|
files:
|
82
|
+
- ".github/workflows/rspec.yml"
|
82
83
|
- ".gitignore"
|
84
|
+
- ".gitlab-ci.yml"
|
83
85
|
- ".rspec"
|
84
86
|
- ".travis.yml"
|
85
87
|
- Gemfile
|
86
|
-
- Gemfile.lock
|
87
88
|
- LICENSE.txt
|
88
89
|
- README.md
|
89
90
|
- Rakefile
|
@@ -99,12 +100,14 @@ files:
|
|
99
100
|
- lib/datacaster/caster.rb
|
100
101
|
- lib/datacaster/checker.rb
|
101
102
|
- lib/datacaster/comparator.rb
|
103
|
+
- lib/datacaster/config.rb
|
104
|
+
- lib/datacaster/definition_context.rb
|
102
105
|
- lib/datacaster/hash_mapper.rb
|
103
106
|
- lib/datacaster/hash_schema.rb
|
107
|
+
- lib/datacaster/message_keys_merger.rb
|
104
108
|
- lib/datacaster/or_node.rb
|
105
109
|
- lib/datacaster/predefined.rb
|
106
110
|
- lib/datacaster/result.rb
|
107
|
-
- lib/datacaster/runner_context.rb
|
108
111
|
- lib/datacaster/terminator.rb
|
109
112
|
- lib/datacaster/then_node.rb
|
110
113
|
- lib/datacaster/transformer.rb
|
@@ -131,7 +134,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
131
134
|
- !ruby/object:Gem::Version
|
132
135
|
version: '0'
|
133
136
|
requirements: []
|
134
|
-
rubygems_version: 3.1
|
137
|
+
rubygems_version: 3.4.1
|
135
138
|
signing_key:
|
136
139
|
specification_version: 4
|
137
140
|
summary: Run-time type checker and transformer for Ruby
|
data/Gemfile.lock
DELETED
@@ -1,59 +0,0 @@
|
|
1
|
-
PATH
|
2
|
-
remote: .
|
3
|
-
specs:
|
4
|
-
datacaster (0.9.0)
|
5
|
-
dry-monads (>= 1.3, < 1.4)
|
6
|
-
|
7
|
-
GEM
|
8
|
-
remote: https://rubygems.org/
|
9
|
-
specs:
|
10
|
-
activemodel (6.0.3.2)
|
11
|
-
activesupport (= 6.0.3.2)
|
12
|
-
activesupport (6.0.3.2)
|
13
|
-
concurrent-ruby (~> 1.0, >= 1.0.2)
|
14
|
-
i18n (>= 0.7, < 2)
|
15
|
-
minitest (~> 5.1)
|
16
|
-
tzinfo (~> 1.1)
|
17
|
-
zeitwerk (~> 2.2, >= 2.2.2)
|
18
|
-
concurrent-ruby (1.1.6)
|
19
|
-
diff-lcs (1.3)
|
20
|
-
dry-core (0.4.9)
|
21
|
-
concurrent-ruby (~> 1.0)
|
22
|
-
dry-equalizer (0.3.0)
|
23
|
-
dry-monads (1.3.5)
|
24
|
-
concurrent-ruby (~> 1.0)
|
25
|
-
dry-core (~> 0.4, >= 0.4.4)
|
26
|
-
dry-equalizer
|
27
|
-
i18n (1.8.3)
|
28
|
-
concurrent-ruby (~> 1.0)
|
29
|
-
minitest (5.14.1)
|
30
|
-
rake (12.3.3)
|
31
|
-
rspec (3.9.0)
|
32
|
-
rspec-core (~> 3.9.0)
|
33
|
-
rspec-expectations (~> 3.9.0)
|
34
|
-
rspec-mocks (~> 3.9.0)
|
35
|
-
rspec-core (3.9.2)
|
36
|
-
rspec-support (~> 3.9.3)
|
37
|
-
rspec-expectations (3.9.2)
|
38
|
-
diff-lcs (>= 1.2.0, < 2.0)
|
39
|
-
rspec-support (~> 3.9.0)
|
40
|
-
rspec-mocks (3.9.1)
|
41
|
-
diff-lcs (>= 1.2.0, < 2.0)
|
42
|
-
rspec-support (~> 3.9.0)
|
43
|
-
rspec-support (3.9.3)
|
44
|
-
thread_safe (0.3.6)
|
45
|
-
tzinfo (1.2.7)
|
46
|
-
thread_safe (~> 0.1)
|
47
|
-
zeitwerk (2.3.0)
|
48
|
-
|
49
|
-
PLATFORMS
|
50
|
-
ruby
|
51
|
-
|
52
|
-
DEPENDENCIES
|
53
|
-
activemodel (>= 5.2)
|
54
|
-
datacaster!
|
55
|
-
rake (>= 12.0)
|
56
|
-
rspec (~> 3.0)
|
57
|
-
|
58
|
-
BUNDLED WITH
|
59
|
-
2.1.4
|