nxt_schema 0.1.0
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 +7 -0
- data/.gitignore +13 -0
- data/.rspec +3 -0
- data/.travis.yml +7 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +86 -0
- data/LICENSE.txt +21 -0
- data/README.md +376 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/nxt_schema/callable.rb +74 -0
- data/lib/nxt_schema/callable_or_value.rb +72 -0
- data/lib/nxt_schema/dsl.rb +38 -0
- data/lib/nxt_schema/error_messages/en.yaml +15 -0
- data/lib/nxt_schema/error_messages.rb +40 -0
- data/lib/nxt_schema/errors/error.rb +7 -0
- data/lib/nxt_schema/errors/invalid_options_error.rb +5 -0
- data/lib/nxt_schema/errors/schema_not_applied_error.rb +5 -0
- data/lib/nxt_schema/errors.rb +4 -0
- data/lib/nxt_schema/node/base.rb +318 -0
- data/lib/nxt_schema/node/collection.rb +73 -0
- data/lib/nxt_schema/node/constructor.rb +9 -0
- data/lib/nxt_schema/node/default_value_evaluator.rb +20 -0
- data/lib/nxt_schema/node/error.rb +13 -0
- data/lib/nxt_schema/node/has_subnodes.rb +97 -0
- data/lib/nxt_schema/node/leaf.rb +43 -0
- data/lib/nxt_schema/node/maybe_evaluator.rb +23 -0
- data/lib/nxt_schema/node/schema.rb +147 -0
- data/lib/nxt_schema/node/template_store.rb +15 -0
- data/lib/nxt_schema/node/type_resolver.rb +24 -0
- data/lib/nxt_schema/node/validate_with_proxy.rb +41 -0
- data/lib/nxt_schema/node.rb +5 -0
- data/lib/nxt_schema/registry.rb +85 -0
- data/lib/nxt_schema/types.rb +10 -0
- data/lib/nxt_schema/undefined.rb +7 -0
- data/lib/nxt_schema/validators/attribute.rb +34 -0
- data/lib/nxt_schema/validators/equality.rb +33 -0
- data/lib/nxt_schema/validators/excluded.rb +23 -0
- data/lib/nxt_schema/validators/excludes.rb +23 -0
- data/lib/nxt_schema/validators/greater_than.rb +23 -0
- data/lib/nxt_schema/validators/greater_than_or_equal.rb +23 -0
- data/lib/nxt_schema/validators/included.rb +23 -0
- data/lib/nxt_schema/validators/includes.rb +23 -0
- data/lib/nxt_schema/validators/less_than.rb +23 -0
- data/lib/nxt_schema/validators/less_than_or_equal.rb +23 -0
- data/lib/nxt_schema/validators/optional_node.rb +26 -0
- data/lib/nxt_schema/validators/pattern.rb +24 -0
- data/lib/nxt_schema/validators/query.rb +28 -0
- data/lib/nxt_schema/validators/registry.rb +11 -0
- data/lib/nxt_schema/validators/validator.rb +17 -0
- data/lib/nxt_schema/version.rb +3 -0
- data/lib/nxt_schema.rb +69 -0
- data/nxt_schema.gemspec +46 -0
- metadata +211 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: '09685f892e03e96e13235c9f5d98c78ddbacd8118a47062c3a4417530cd2a3b0'
|
4
|
+
data.tar.gz: ec1b647208395d39dc5f61639dcfdfa1ea65caadfa9cd9011414ed9a5a5d9430
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 56489e75d59dc6f21f8237fcebc14236113c2e14cb47adb986989f819d0937353b1dd37763b522737923e3a87966eda2bf339f42055383fc8d174d536f1d32e5
|
7
|
+
data.tar.gz: 1f595737e27ff2c874c807b2339eee3781799a64cac27bd3724642898db6200ec60279edd72b50f100d3a52ff4f8ef155e97392b8738186258d8fd93dbdd65b0
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
nxt_schema (0.1.0)
|
5
|
+
activesupport
|
6
|
+
dry-types
|
7
|
+
nxt_registry
|
8
|
+
|
9
|
+
GEM
|
10
|
+
remote: https://rubygems.org/
|
11
|
+
specs:
|
12
|
+
activesupport (6.0.2.1)
|
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)
|
18
|
+
coderay (1.1.2)
|
19
|
+
concurrent-ruby (1.1.6)
|
20
|
+
diff-lcs (1.3)
|
21
|
+
dry-configurable (0.11.2)
|
22
|
+
concurrent-ruby (~> 1.0)
|
23
|
+
dry-core (~> 0.4, >= 0.4.7)
|
24
|
+
dry-equalizer (~> 0.2)
|
25
|
+
dry-container (0.7.2)
|
26
|
+
concurrent-ruby (~> 1.0)
|
27
|
+
dry-configurable (~> 0.1, >= 0.1.3)
|
28
|
+
dry-core (0.4.9)
|
29
|
+
concurrent-ruby (~> 1.0)
|
30
|
+
dry-equalizer (0.3.0)
|
31
|
+
dry-inflector (0.2.0)
|
32
|
+
dry-logic (1.0.6)
|
33
|
+
concurrent-ruby (~> 1.0)
|
34
|
+
dry-core (~> 0.2)
|
35
|
+
dry-equalizer (~> 0.2)
|
36
|
+
dry-types (1.3.1)
|
37
|
+
concurrent-ruby (~> 1.0)
|
38
|
+
dry-container (~> 0.3)
|
39
|
+
dry-core (~> 0.4, >= 0.4.4)
|
40
|
+
dry-equalizer (~> 0.3)
|
41
|
+
dry-inflector (~> 0.1, >= 0.1.2)
|
42
|
+
dry-logic (~> 1.0, >= 1.0.2)
|
43
|
+
hirb (0.7.3)
|
44
|
+
i18n (1.8.2)
|
45
|
+
concurrent-ruby (~> 1.0)
|
46
|
+
method_profiler (2.0.1)
|
47
|
+
hirb (>= 0.6.0)
|
48
|
+
method_source (0.9.2)
|
49
|
+
minitest (5.14.0)
|
50
|
+
nxt_registry (0.1.4)
|
51
|
+
activesupport
|
52
|
+
pry (0.12.2)
|
53
|
+
coderay (~> 1.1.0)
|
54
|
+
method_source (~> 0.9.0)
|
55
|
+
rake (12.3.3)
|
56
|
+
rspec (3.8.0)
|
57
|
+
rspec-core (~> 3.8.0)
|
58
|
+
rspec-expectations (~> 3.8.0)
|
59
|
+
rspec-mocks (~> 3.8.0)
|
60
|
+
rspec-core (3.8.2)
|
61
|
+
rspec-support (~> 3.8.0)
|
62
|
+
rspec-expectations (3.8.4)
|
63
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
64
|
+
rspec-support (~> 3.8.0)
|
65
|
+
rspec-mocks (3.8.1)
|
66
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
67
|
+
rspec-support (~> 3.8.0)
|
68
|
+
rspec-support (3.8.2)
|
69
|
+
thread_safe (0.3.6)
|
70
|
+
tzinfo (1.2.6)
|
71
|
+
thread_safe (~> 0.1)
|
72
|
+
zeitwerk (2.2.2)
|
73
|
+
|
74
|
+
PLATFORMS
|
75
|
+
ruby
|
76
|
+
|
77
|
+
DEPENDENCIES
|
78
|
+
bundler (~> 1.17)
|
79
|
+
method_profiler
|
80
|
+
nxt_schema!
|
81
|
+
pry
|
82
|
+
rake (~> 12.3.3)
|
83
|
+
rspec (~> 3.0)
|
84
|
+
|
85
|
+
BUNDLED WITH
|
86
|
+
1.17.2
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2019 Andreas Robecke
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,376 @@
|
|
1
|
+
# NxtSchema
|
2
|
+
|
3
|
+
## Installation
|
4
|
+
|
5
|
+
Add this line to your application's Gemfile:
|
6
|
+
|
7
|
+
```ruby
|
8
|
+
gem 'nxt_schema'
|
9
|
+
```
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install nxt_schema
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
```ruby
|
22
|
+
# Schema with hash root
|
23
|
+
schema = NxtSchema.root(:company) do
|
24
|
+
requires(:name, :String)
|
25
|
+
requires(:value, :Integer).maybe(nil)
|
26
|
+
present(:stock_options, :Bool).default(false)
|
27
|
+
|
28
|
+
schema(:address) do
|
29
|
+
requires(:street, :String)
|
30
|
+
requires(:street_number, :Integer)
|
31
|
+
end
|
32
|
+
|
33
|
+
nodes(:employees) do
|
34
|
+
hash(:employee) do
|
35
|
+
POSITIONS = %w[senior junior intern]
|
36
|
+
|
37
|
+
requires(:first_name, :String)
|
38
|
+
requires(:last_name, :String)
|
39
|
+
optional(:email, :String).validate(:format, /\A.*@.*\z/)
|
40
|
+
requires(:position, NxtSchema::Types::Enums[*POSITIONS])
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# Schema with array root
|
46
|
+
schema = NxtSchema.roots(:companies) do
|
47
|
+
schema(:company) do
|
48
|
+
requires(:name, :String)
|
49
|
+
requires(:value, :Integer).maybe(nil)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
schema.apply(your: 'values here')
|
54
|
+
schema.errors # { 'name.spaced.key': ['all the errors'] }
|
55
|
+
```
|
56
|
+
|
57
|
+
### DSL
|
58
|
+
|
59
|
+
Create a new schema with `NxtSchema.root { ... }` or in case you have an array node as root,
|
60
|
+
use `NxtSchema.roots { ... }`. Within the schema you can create node simply with the `node(name, type_or_node, **options)`
|
61
|
+
method. Each node requires a name and a type and accepts additional options. Node are required per default.
|
62
|
+
But you can make them optional by providing the optional option.
|
63
|
+
|
64
|
+
#### Nodes
|
65
|
+
|
66
|
+
```ruby
|
67
|
+
NxtSchema.root do
|
68
|
+
node(:first_name, :String)
|
69
|
+
node(:last_name, :String, optional: true)
|
70
|
+
node(:email, :String, presence: true)
|
71
|
+
end
|
72
|
+
```
|
73
|
+
|
74
|
+
In order to make the schema more readable you can make use of several predicate aliases to create required, optional or
|
75
|
+
(omni)present nodes.
|
76
|
+
|
77
|
+
#### Predicate aliases
|
78
|
+
|
79
|
+
```ruby
|
80
|
+
NxtSchema.root do
|
81
|
+
required(:first_name, :String)
|
82
|
+
optional(:last_name, :String)
|
83
|
+
present(:email, :String)
|
84
|
+
end
|
85
|
+
```
|
86
|
+
|
87
|
+
### Nodes
|
88
|
+
|
89
|
+
The following types of nodes exist
|
90
|
+
|
91
|
+
#### Schema Nodes
|
92
|
+
|
93
|
+
```ruby
|
94
|
+
# Create schema nodes with:
|
95
|
+
required(:test, :Schema) do ... end
|
96
|
+
schema(:test) do ... end
|
97
|
+
hash(:test) do ... end
|
98
|
+
```
|
99
|
+
|
100
|
+
#### Collection Nodes
|
101
|
+
|
102
|
+
```ruby
|
103
|
+
# Create collection (array) nodes with:
|
104
|
+
required(:test, :Collection) do ... end
|
105
|
+
|
106
|
+
nodes(:test) do
|
107
|
+
# For type checking of array items you can simply add a node with the expected type.
|
108
|
+
# As always you need to give it a name. This would result in an array of string items
|
109
|
+
required(:item, :String)
|
110
|
+
end
|
111
|
+
|
112
|
+
array(:test) do ... end
|
113
|
+
```
|
114
|
+
|
115
|
+
#### Leaf Nodes
|
116
|
+
|
117
|
+
```ruby
|
118
|
+
# Create leaf nodes with a basic type
|
119
|
+
required(:test, :String) do ... end
|
120
|
+
```
|
121
|
+
|
122
|
+
#### Struct Nodes
|
123
|
+
|
124
|
+
```ruby
|
125
|
+
# Create structs from hash inputs
|
126
|
+
struct(:test) do ... end
|
127
|
+
```
|
128
|
+
|
129
|
+
### Types
|
130
|
+
|
131
|
+
The type system is built with dry-types from the amazing https://dry-rb.org/ eco system. Even though dry-types also
|
132
|
+
offers features such as default values for types as well as maybe types, these features are built directly into
|
133
|
+
NxtSchema. Dry.rb also has a gem for schemas and another one dedicated to validations. You should probably
|
134
|
+
check those out! However, in NxtSchema every node has a type and you can either provide a symbol that will be resolved
|
135
|
+
through the type system of the schema. But you can also directly provide an instance of dry type and thus use your
|
136
|
+
custom types.
|
137
|
+
|
138
|
+
#### Default type system
|
139
|
+
|
140
|
+
You can tell your schema which default type system it should use. Dry-Types comes with a few built in type systems.
|
141
|
+
Per default NxtSchema will use nominal types if not specified otherwise. If the type cannot be resolved from the default
|
142
|
+
type system that was specified, NxtSchema will again try to fallback to nominal types. In theory you can provide
|
143
|
+
a separate type system per node if that's what you want :-D
|
144
|
+
|
145
|
+
```ruby
|
146
|
+
NxtSchema.root do
|
147
|
+
required(:test, :String) # The :String will resolve to NxtSchema::Types::Nominal::String
|
148
|
+
end
|
149
|
+
|
150
|
+
NxtSchema.root(type_system: NxtSchema::Types::JSON) do
|
151
|
+
required(:test, :Date) # The :Date will resolve to NxtSchema::Types::JSON::Date
|
152
|
+
# When the type does not exist in the default type system (there is non JSON::String) we fallback to nominal types
|
153
|
+
required(:test, :String)
|
154
|
+
end
|
155
|
+
```
|
156
|
+
|
157
|
+
#### NxtSchema.params
|
158
|
+
|
159
|
+
NxtSchema.params will give you a schema as root node with NxtSchema::Types::Params as default type system.
|
160
|
+
This is suitable to validate and coerce your query params.
|
161
|
+
|
162
|
+
```ruby
|
163
|
+
NxtSchema.params do
|
164
|
+
required(:effective_at, :DateTime) # would resolve to Types::Params::DateTime
|
165
|
+
required(:test, :String) # The :String will resolve to NxtSchema::Types::Nominal::String
|
166
|
+
required(:advanced, NxtSchema::Types::Params::Bool) # long version of required(:advanced, :Bool)
|
167
|
+
end
|
168
|
+
```
|
169
|
+
|
170
|
+
#### Custom types
|
171
|
+
|
172
|
+
You can also register custom types. In order to check out all the cool things you can do with dry types you should
|
173
|
+
check out dry-types on https://dry-rb.org. But here is how you can add a type to the `NxtSchema::Types` module.
|
174
|
+
|
175
|
+
```ruby
|
176
|
+
NxtSchema.register_type(
|
177
|
+
:MyCustomStrippedString,
|
178
|
+
NxtSchema::Types::Strict::String.constructor(->(string) { string&.strip })
|
179
|
+
)
|
180
|
+
|
181
|
+
# once registered you can use the type in your schema
|
182
|
+
|
183
|
+
NxtSchema.root(:company) do
|
184
|
+
required(:name, NxtSchema::Types::MyCustomStrippedString)
|
185
|
+
end
|
186
|
+
```
|
187
|
+
|
188
|
+
### Values
|
189
|
+
|
190
|
+
#### Default values
|
191
|
+
|
192
|
+
```ruby
|
193
|
+
# Define default values as options or with the default method
|
194
|
+
required(:test, :String).default(value_or_proc)
|
195
|
+
required(:test, :String, default: value_or_proc) do ... end
|
196
|
+
```
|
197
|
+
|
198
|
+
#### Maybe values
|
199
|
+
|
200
|
+
Allow specific values that are not being coerced
|
201
|
+
|
202
|
+
```ruby
|
203
|
+
# Define maybe values (values that do not match the type)
|
204
|
+
required(:test, :String).maybe(value_or_proc)
|
205
|
+
required(:test, :String, maybe: value_or_proc) do ... end
|
206
|
+
```
|
207
|
+
|
208
|
+
### Validations
|
209
|
+
|
210
|
+
NxtSchema comes with a simple validation system and ships with a small set of useful validators. Every node in a schema
|
211
|
+
implements the `:validate` method. Similar to ActiveModel::Validations it allows you to simply add errors to a node
|
212
|
+
based on some condition.
|
213
|
+
|
214
|
+
```ruby
|
215
|
+
# Simple validation
|
216
|
+
required(:test, :String).validate -> (node, value) { node.add_error("#{value} is not valid") if value == 'not allowed' }
|
217
|
+
# Built in validations
|
218
|
+
required(:test, :String).validate(:attribute, :size, ->(s) { s < 7 })
|
219
|
+
required(:test, :String).validate(:equality, 'same')
|
220
|
+
required(:test, :String).validate(:excluded, %w[not_allowed]) # excluded in the target: %w[not_allowed]
|
221
|
+
required(:test, :String).validate(:included, %w[allowed]) # included in the target: %w[allowed]
|
222
|
+
required(:test, :Array).validate(:excludes, 'excluded') # array value itself must exclude 'excluded'
|
223
|
+
required(:test, :Array).validate(:includes, 'included') # array value itself must include 'included'
|
224
|
+
required(:test, :Integer).validate(:greater_than, 1)
|
225
|
+
required(:test, :Integer).validate(:greater_than_or_equal, 1)
|
226
|
+
required(:test, :Integer).validate(:less_than, 1)
|
227
|
+
required(:test, :Integer).validate(:less_than_or_equal, 1)
|
228
|
+
required(:test, :String).validate(:pattern, /\A.*@.*\z/)
|
229
|
+
required(:test, :String).validate(:query, :present?)
|
230
|
+
```
|
231
|
+
|
232
|
+
#### Custom validators
|
233
|
+
|
234
|
+
You can also register your custom validators. Therefore you can simply implement a class that returns a lambda on build.
|
235
|
+
This lambda will be called with the node the validations runs on and it's input value. Except this, you are free in
|
236
|
+
how your validator can be used. Check out the specs for some examples.
|
237
|
+
|
238
|
+
```ruby
|
239
|
+
class MyCustomExclusionValidator
|
240
|
+
def initialize(target)
|
241
|
+
@target = target
|
242
|
+
end
|
243
|
+
|
244
|
+
attr_reader :target
|
245
|
+
|
246
|
+
def build
|
247
|
+
lambda do |node, value|
|
248
|
+
if target.exclude?(value)
|
249
|
+
true
|
250
|
+
else
|
251
|
+
node.add_error("#{target} should not contain #{value}")
|
252
|
+
false # validators must return false in the bad case (add_error already does this as per default)
|
253
|
+
end
|
254
|
+
end
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
# Register your validators
|
259
|
+
NxtSchema.register_validator(MyCustomExclusionValidator, :my_custom_exclusion_validator)
|
260
|
+
|
261
|
+
# and then simply reference it with the key you've registered it
|
262
|
+
schema = NxtSchema.root(:company) do
|
263
|
+
requires(:name, :String).validate(:my_custom_exclusion_validator, %w[lemonade])
|
264
|
+
end
|
265
|
+
|
266
|
+
schema.apply(name: 'lemonade').valid? # => false
|
267
|
+
```
|
268
|
+
|
269
|
+
#### Validation messages
|
270
|
+
|
271
|
+
- Allow to specify a path to translations
|
272
|
+
- Add translated errors
|
273
|
+
- Interpolate with actual vs. expected
|
274
|
+
|
275
|
+
#### Combining validators with custom logic
|
276
|
+
|
277
|
+
`node(:test, String).validate(...)` basically adds a validator to the node. Of course you can add multiple validators.
|
278
|
+
But that means that they will all be executed and errors aggregated. If you want your validator to only run in case
|
279
|
+
another was false, you can use `:validat_with do ... end` in order to combine validators based on custom logic.
|
280
|
+
|
281
|
+
```ruby
|
282
|
+
NxtSchema.root do
|
283
|
+
required(:test, :Integer).validate_with do
|
284
|
+
validator(:greater_than, 5) &&
|
285
|
+
validator(:greater_than, 6) &&
|
286
|
+
validator(:greater_than, 7)
|
287
|
+
end
|
288
|
+
end
|
289
|
+
```
|
290
|
+
|
291
|
+
This has one drawback however. Let's say your test value is 4. This would only run your first validator and then exit
|
292
|
+
from the logic since validators are combined with &&. In this example it might not make much sense, but it basically
|
293
|
+
means that you might not have the full validation errors when combining validations with `:validate_with`
|
294
|
+
|
295
|
+
|
296
|
+
### Schema options
|
297
|
+
|
298
|
+
#### Optional keys strategies
|
299
|
+
|
300
|
+
Schemas in NxtSchema only look at the keys that you have defined in your schema, others are ignored per default.
|
301
|
+
You can change this behaviour by providing a strategy for the `:additional_keys` option.
|
302
|
+
|
303
|
+
```ruby
|
304
|
+
# This will simply ignore any other key except test
|
305
|
+
NxtSchema.root(additional_keys: :ignore) do
|
306
|
+
required(:test, :String)
|
307
|
+
end
|
308
|
+
|
309
|
+
# This would give you an error in case you apply anything other than { test: '...' }
|
310
|
+
NxtSchema.root(additional_keys: :restrict) do
|
311
|
+
required(:test, :String)
|
312
|
+
end
|
313
|
+
|
314
|
+
# This will merge other keys into your output
|
315
|
+
schema = NxtSchema.root(additional_keys: :allow) do
|
316
|
+
required(:test, :String)
|
317
|
+
end
|
318
|
+
|
319
|
+
schema.apply(test: 'getsafe', other: 'Heidelberg')
|
320
|
+
schema.valid? # => true
|
321
|
+
schema.value # => { test: 'getsafe', other: 'Heidelberg' }
|
322
|
+
```
|
323
|
+
|
324
|
+
#### Transform keys
|
325
|
+
|
326
|
+
You may want to transform the keys from your input. Therefore specify the transform_keys option. This might be useful
|
327
|
+
when you want your schema to return only symbolized keys for example.
|
328
|
+
|
329
|
+
```ruby
|
330
|
+
schema = NxtSchema.root(transform_keys: :to_sym) do
|
331
|
+
required(:test, :String)
|
332
|
+
end
|
333
|
+
|
334
|
+
schema.apply('test' => 'getsafe') # => {:test=>"getsafe"}
|
335
|
+
schema.apply(test: 'getsafe') # => {:test=>"getsafe"}
|
336
|
+
```
|
337
|
+
|
338
|
+
#### Adding meta data to nodes
|
339
|
+
|
340
|
+
You want to give nodes an ID or some other meta data? You can use the meta method on nodes for adding additional
|
341
|
+
information onto any node.
|
342
|
+
|
343
|
+
```ruby
|
344
|
+
schema = NxtSchema.root do
|
345
|
+
ERROR_MESSAGES = {
|
346
|
+
test: 'This is always broken'
|
347
|
+
}
|
348
|
+
|
349
|
+
required(:test, :String).meta(ERROR_MESSAGES).validate ->(node) { node.add_error(node.meta.fetch(node.name)) }
|
350
|
+
end
|
351
|
+
|
352
|
+
schema.apply(test: 'getsafe')
|
353
|
+
schema.error # {"root.test"=>["This is always broken"]}
|
354
|
+
```
|
355
|
+
|
356
|
+
## Development
|
357
|
+
|
358
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
359
|
+
|
360
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
361
|
+
|
362
|
+
## Contributing
|
363
|
+
|
364
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/nxt_schema.
|
365
|
+
|
366
|
+
## License
|
367
|
+
|
368
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
369
|
+
|
370
|
+
# TODO:
|
371
|
+
|
372
|
+
- Explain the difference between array nodes and typed array nodes
|
373
|
+
- Should we translate coercion errors as well?
|
374
|
+
- Test the different scenarios of merging schemas array, hash, ...
|
375
|
+
- Structure Errors
|
376
|
+
- NxtSchema::Json => Use json types, maybe even parse Json with Oj
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "nxt_schema"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
module NxtSchema
|
2
|
+
class Callable
|
3
|
+
def initialize(callee)
|
4
|
+
@callee = callee
|
5
|
+
|
6
|
+
if callee.is_a?(Symbol)
|
7
|
+
self.type = :method
|
8
|
+
elsif callee.respond_to?(:call)
|
9
|
+
self.type = :proc
|
10
|
+
self.context = callee.binding
|
11
|
+
else
|
12
|
+
raise ArgumentError, "Callee is nor symbol nor a proc: #{callee}"
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def bind!(execution_context)
|
17
|
+
self.context = execution_context
|
18
|
+
ensure_context_not_missing
|
19
|
+
self
|
20
|
+
end
|
21
|
+
|
22
|
+
def bind(execution_context = nil)
|
23
|
+
return self if context
|
24
|
+
|
25
|
+
self.context = execution_context
|
26
|
+
ensure_context_not_missing
|
27
|
+
self
|
28
|
+
end
|
29
|
+
|
30
|
+
# NOTE: Currently we only allow arguments! Not keyword args or **options
|
31
|
+
# If we would allow **options and we would pass a hash as the only argument it would
|
32
|
+
# automatically be parsed as the options!
|
33
|
+
def call(*args)
|
34
|
+
ensure_context_not_missing
|
35
|
+
|
36
|
+
args = args.take(arity)
|
37
|
+
|
38
|
+
if method?
|
39
|
+
context.send(callee, *args)
|
40
|
+
else
|
41
|
+
context.instance_exec(*args, &callee)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def arity
|
46
|
+
if proc?
|
47
|
+
callee.arity
|
48
|
+
elsif method?
|
49
|
+
method = context.send(:method, callee)
|
50
|
+
method.arity
|
51
|
+
else
|
52
|
+
raise ArgumentError, "Can't resolve arity from #{callee}"
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def proc?
|
59
|
+
type == :proc
|
60
|
+
end
|
61
|
+
|
62
|
+
def method?
|
63
|
+
type == :method
|
64
|
+
end
|
65
|
+
|
66
|
+
def ensure_context_not_missing
|
67
|
+
return if context
|
68
|
+
|
69
|
+
raise ArgumentError, "Missing context: #{context}"
|
70
|
+
end
|
71
|
+
|
72
|
+
attr_accessor :context, :callee, :type
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
module NxtSchema
|
2
|
+
class CallableOrValue
|
3
|
+
def initialize(callee)
|
4
|
+
@callee = callee
|
5
|
+
|
6
|
+
if callee.is_a?(Symbol)
|
7
|
+
self.type = :method
|
8
|
+
elsif callee.respond_to?(:call)
|
9
|
+
self.type = :proc
|
10
|
+
self.context = callee.binding
|
11
|
+
else
|
12
|
+
self.type = :value
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def bind(execution_context = nil)
|
17
|
+
self.context = execution_context
|
18
|
+
ensure_context_not_missing
|
19
|
+
self
|
20
|
+
end
|
21
|
+
|
22
|
+
# NOTE: Currently we only allow arguments! Not keyword args or **options
|
23
|
+
# If we would allow **options and we would pass a hash as the only argument it would
|
24
|
+
# automatically be parsed as the options!
|
25
|
+
def call(*args)
|
26
|
+
return callee if value?
|
27
|
+
|
28
|
+
ensure_context_not_missing
|
29
|
+
|
30
|
+
args = args.take(arity)
|
31
|
+
|
32
|
+
if method?
|
33
|
+
context.send(callee, *args)
|
34
|
+
else
|
35
|
+
context.instance_exec(*args, &callee)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def arity
|
40
|
+
if proc?
|
41
|
+
callee.arity
|
42
|
+
elsif method?
|
43
|
+
method = context.send(:method, callee)
|
44
|
+
method.arity
|
45
|
+
else
|
46
|
+
raise ArgumentError, "Can't resolve arity from #{callee}"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def proc?
|
53
|
+
type == :proc
|
54
|
+
end
|
55
|
+
|
56
|
+
def method?
|
57
|
+
type == :method
|
58
|
+
end
|
59
|
+
|
60
|
+
def value?
|
61
|
+
type == :value
|
62
|
+
end
|
63
|
+
|
64
|
+
def ensure_context_not_missing
|
65
|
+
return if context
|
66
|
+
|
67
|
+
raise ArgumentError, "Missing context: #{context}"
|
68
|
+
end
|
69
|
+
|
70
|
+
attr_accessor :context, :callee, :type
|
71
|
+
end
|
72
|
+
end
|