rschema 1.0.1 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +103 -43
- data/lib/rschema.rb +30 -27
- data/lib/rschema/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cb8c9bd48727609ab023125d1e36c302db187f18
|
4
|
+
data.tar.gz: f78899c23ad12c9b089b8789a3c5b1cfd36daf20
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9e190a4eb7fd75bf1a119f31aa29f3265dd4750b3732848c13468ca5226edaed503bc28bfdfde73a1cb4b4d7bbe1a20674586431d7991efb7eda9ab9162bd332
|
7
|
+
data.tar.gz: edc80b54868d241d2ad2e83dceacaddd4d23c750f700a1707126f35f2b676fb9c221f0f8c96e2136fd69bb356edc15bbf702c7c4f1a5a81f257e6b09c383e04e
|
data/README.md
CHANGED
@@ -21,11 +21,11 @@ Schemas can be used to validate data. That is, they can check whether
|
|
21
21
|
data is in the correct shape:
|
22
22
|
|
23
23
|
```ruby
|
24
|
-
RSchema.validate
|
24
|
+
RSchema.validate(post_schema, {
|
25
25
|
title: "You won't beleive how this developer foo'd her bar",
|
26
26
|
tags: [:foos, :bars, :unbeleivable],
|
27
27
|
body: '<p>blah blah</p>'
|
28
|
-
})
|
28
|
+
}) #=> true
|
29
29
|
```
|
30
30
|
|
31
31
|
What is a schema?
|
@@ -35,8 +35,8 @@ Schemas are Ruby data structures. The simplest type of schema is just a class:
|
|
35
35
|
|
36
36
|
```ruby
|
37
37
|
schema = Integer
|
38
|
-
RSchema.validate
|
39
|
-
RSchema.validate
|
38
|
+
RSchema.validate(schema, 5) #=> true
|
39
|
+
RSchema.validate(schema, 'hello') #=> false
|
40
40
|
```
|
41
41
|
|
42
42
|
Then there are composite schemas, which are schemas composed of subschemas.
|
@@ -44,16 +44,16 @@ Arrays are composite schemas:
|
|
44
44
|
|
45
45
|
```ruby
|
46
46
|
schema = [Integer]
|
47
|
-
RSchema.validate
|
48
|
-
RSchema.validate
|
47
|
+
RSchema.validate(schema, [10, 11, 12]) #=> true
|
48
|
+
RSchema.validate(schema, [10, 11, '12']) #=> false
|
49
49
|
```
|
50
50
|
|
51
51
|
And so are hashes:
|
52
52
|
|
53
53
|
```ruby
|
54
54
|
schema = { fname: String, age: Integer }
|
55
|
-
RSchema.validate
|
56
|
-
RSchema.validate
|
55
|
+
RSchema.validate(schema, { fname: 'Jane', age: 27 }) #=> true
|
56
|
+
RSchema.validate(schema, { fname: 'Johnny' }) #=> false
|
57
57
|
```
|
58
58
|
|
59
59
|
While schemas are just plain old Ruby data structures, RSchema also provides
|
@@ -66,14 +66,67 @@ schema = RSchema.schema {{
|
|
66
66
|
children_by_age: hash_of(Integer => String)
|
67
67
|
}}
|
68
68
|
|
69
|
-
RSchema.validate
|
69
|
+
RSchema.validate(schema, {
|
70
70
|
fname: 'Johnny',
|
71
71
|
favourite_foods: Set.new([:bacon, :cheese, :onion]),
|
72
72
|
children_by_age: {
|
73
73
|
7 => 'Jenny',
|
74
74
|
5 => 'Simon'
|
75
75
|
}
|
76
|
-
})
|
76
|
+
}) #=> true
|
77
|
+
```
|
78
|
+
|
79
|
+
When Validation Fails
|
80
|
+
---------------------
|
81
|
+
|
82
|
+
Using `RSchema.validate`, it is often difficult to tell exactly which values
|
83
|
+
are failing validation.
|
84
|
+
|
85
|
+
```ruby
|
86
|
+
schema = RSchema.schema do
|
87
|
+
[
|
88
|
+
{
|
89
|
+
name: String,
|
90
|
+
hair: enum([:red, :brown, :blonde, :black])
|
91
|
+
}
|
92
|
+
]
|
93
|
+
end
|
94
|
+
|
95
|
+
value = [
|
96
|
+
{ name: 'Dane', hair: :black },
|
97
|
+
{ name: 'Tom', hair: :brown },
|
98
|
+
{ name: 'Effie', hair: :blond },
|
99
|
+
{ name: 'Chris', hair: :red },
|
100
|
+
]
|
101
|
+
|
102
|
+
RSchema.validate(schema, value) #=> false
|
103
|
+
```
|
104
|
+
|
105
|
+
To see exactly where validation fails, we can look at an
|
106
|
+
`RSchema::ErrorDetails` object.
|
107
|
+
|
108
|
+
The `validate!` method throws an exception when validation fails, and the
|
109
|
+
exception contains the `RSchema::ErrorDetails` object.
|
110
|
+
|
111
|
+
```ruby
|
112
|
+
RSchema.validate!(schema, value) # throws exception:
|
113
|
+
#=> RSchema::ValidationError: The value at [2, :hair] is not a valid enum member: :blond
|
114
|
+
```
|
115
|
+
|
116
|
+
The error above says that the value `:blond`, which exists at location
|
117
|
+
`value[2][:hair]`, is not a valid enum member. Looking back at the schema, we
|
118
|
+
see that there is a typo, and it should be `:blonde` instead of `:blond`.
|
119
|
+
|
120
|
+
To get an `RSchema::ErrorDetails` object _without_ using exceptions, we can use
|
121
|
+
the `RSchema.validation_error` method.
|
122
|
+
|
123
|
+
```ruby
|
124
|
+
error_details = RSchema.validation_error(schema, value)
|
125
|
+
|
126
|
+
error_details.failing_value #=> :blond
|
127
|
+
error_details.reason #=> "is not a valid enum member"
|
128
|
+
error_details.key_path #=> [2, :hair]
|
129
|
+
error_details.to_s #=> "The value at [2, :hair] is not a valid enum member: :blond"
|
77
130
|
```
|
78
131
|
|
79
132
|
Array Schemas
|
@@ -84,18 +137,18 @@ element, it is a variable-length array schema:
|
|
84
137
|
|
85
138
|
```ruby
|
86
139
|
schema = [Symbol]
|
87
|
-
RSchema.validate
|
88
|
-
RSchema.validate
|
89
|
-
RSchema.validate
|
140
|
+
RSchema.validate(schema, [:a, :b, :c]) #=> true
|
141
|
+
RSchema.validate(schema, [:a]) #=> true
|
142
|
+
RSchema.validate(schema, []) #=> true
|
90
143
|
```
|
91
144
|
|
92
145
|
Otherwise, it is a fixed-length array schema
|
93
146
|
|
94
147
|
```ruby
|
95
148
|
schema = [Integer, String]
|
96
|
-
RSchema.validate
|
97
|
-
RSchema.validate
|
98
|
-
RSchema.validate
|
149
|
+
RSchema.validate(schema, [10, 'hello']) #=> true
|
150
|
+
RSchema.validate(schema, [10, 'hello', 'world']) #=> false
|
151
|
+
RSchema.validate(schema, [10]) #=> false
|
99
152
|
```
|
100
153
|
|
101
154
|
Hash Schemas
|
@@ -105,7 +158,7 @@ Hash schemas map constant keys to subschema values:
|
|
105
158
|
|
106
159
|
```ruby
|
107
160
|
schema = { fname: String }
|
108
|
-
RSchema.validate
|
161
|
+
RSchema.validate(schema, { fname: 'William' }) #=> true
|
109
162
|
```
|
110
163
|
|
111
164
|
Keys can be optional:
|
@@ -115,8 +168,8 @@ schema = RSchema.schema {{
|
|
115
168
|
:fname => String,
|
116
169
|
_?(:age) => Integer
|
117
170
|
}}
|
118
|
-
RSchema.validate
|
119
|
-
RSchema.validate
|
171
|
+
RSchema.validate(schema, { fname: 'Lucy', age: 21 }) #=> true
|
172
|
+
RSchema.validate(schema, { fname: 'Tom' }) #=> true
|
120
173
|
```
|
121
174
|
|
122
175
|
There is also another type of hash schema that represents hashes with variable
|
@@ -124,9 +177,9 @@ keys:
|
|
124
177
|
|
125
178
|
```ruby
|
126
179
|
schema = RSchema.schema { hash_of(String => Integer) }
|
127
|
-
RSchema.validate
|
128
|
-
RSchema.validate
|
129
|
-
RSchema.validate
|
180
|
+
RSchema.validate(schema, { 'hello' => 1, 'world' => 2 }) #=> true
|
181
|
+
RSchema.validate(schema, { 'hello' => 1 }) #=> true
|
182
|
+
RSchema.validate(schema, {}) #=> true
|
130
183
|
```
|
131
184
|
|
132
185
|
Other Schema Types
|
@@ -137,25 +190,25 @@ RSchema provides a few other schema types through its DSL:
|
|
137
190
|
```ruby
|
138
191
|
# boolean
|
139
192
|
boolean_schema = RSchema.schema{ boolean }
|
140
|
-
RSchema.validate
|
141
|
-
RSchema.validate
|
193
|
+
RSchema.validate(boolean_schema, false) #=> true
|
194
|
+
RSchema.validate(boolean_schema, nil) #=> false
|
142
195
|
|
143
196
|
# maybe
|
144
197
|
maybe_schema = RSchema.schema{ maybe(Integer) }
|
145
|
-
RSchema.validate
|
146
|
-
RSchema.validate
|
198
|
+
RSchema.validate(maybe_schema, 5) #=> true
|
199
|
+
RSchema.validate(maybe_schema, nil) #=> true
|
147
200
|
|
148
201
|
# enum
|
149
202
|
enum_schema = RSchema.schema{ enum([:a, :b, :c]) }
|
150
|
-
RSchema.validate
|
151
|
-
RSchema.validate
|
203
|
+
RSchema.validate(enum_schema, :a) #=> true
|
204
|
+
RSchema.validate(enum_schema, :z) #=> false
|
152
205
|
|
153
206
|
# predicate
|
154
207
|
predicate_schema = RSchema.schema do
|
155
208
|
predicate('even') { |x| x.even? }
|
156
209
|
end
|
157
|
-
RSchema.validate
|
158
|
-
RSchema.validate
|
210
|
+
RSchema.validate(predicate_schema, 4) #=> true
|
211
|
+
RSchema.validate(predicate_schema, 5) #=> false
|
159
212
|
```
|
160
213
|
|
161
214
|
Coercion
|
@@ -188,18 +241,23 @@ RSchema.coerce!(schema, value)
|
|
188
241
|
Extending the DSL
|
189
242
|
-----------------
|
190
243
|
|
191
|
-
|
244
|
+
You can create new, custom DSLs that extend the default DSL like so:
|
192
245
|
|
193
246
|
```ruby
|
194
|
-
module
|
247
|
+
module MyCustomDSL
|
248
|
+
extend RSchema::DSL::Base
|
195
249
|
def self.positive_and_even(type)
|
196
250
|
predicate { |x| x > 0 && x.even? }
|
197
251
|
end
|
198
252
|
end
|
253
|
+
```
|
199
254
|
|
200
|
-
|
201
|
-
|
202
|
-
|
255
|
+
Pass the custom DSL to `RSchema.schema` to use it:
|
256
|
+
|
257
|
+
```ruby
|
258
|
+
schema = RSchema.schema(MyCustomDSL) { positive_and_even }
|
259
|
+
RSchema.validate(schema, 6) #=> true
|
260
|
+
RSchema.validate(schema, -6) #=> false
|
203
261
|
```
|
204
262
|
|
205
263
|
Custom Schema Types
|
@@ -214,16 +272,16 @@ in an array:
|
|
214
272
|
class CoordinateSchema
|
215
273
|
def schema_walk(value, mapper)
|
216
274
|
# validate `value`
|
217
|
-
return RSchema::ErrorDetails.new('is not an Array') unless value.is_a?(Array)
|
218
|
-
return RSchema::ErrorDetails.new('does not have two elements') unless value.size == 2
|
275
|
+
return RSchema::ErrorDetails.new(value, 'is not an Array') unless value.is_a?(Array)
|
276
|
+
return RSchema::ErrorDetails.new(value, 'does not have two elements') unless value.size == 2
|
219
277
|
|
220
278
|
# walk the subschemas/subvalues
|
221
279
|
x, x_error = RSchema.walk(Float, value[0], mapper)
|
222
280
|
y, y_error = RSchema.walk(Float, value[1], mapper)
|
223
281
|
|
224
282
|
# look for subschema errors, and propagate them if found
|
225
|
-
return
|
226
|
-
return
|
283
|
+
return x_error.extend_key_path(:x) if x_error
|
284
|
+
return y_error.extend_key_path(:y) if y_error
|
227
285
|
|
228
286
|
# return the valid value
|
229
287
|
[x, y]
|
@@ -239,9 +297,9 @@ end
|
|
239
297
|
|
240
298
|
# use the custom schema type (coercion works too)
|
241
299
|
schema = RSchema.schema { coordinate }
|
242
|
-
RSchema.validate
|
243
|
-
RSchema.validate
|
244
|
-
RSchema.coerce!(schema, ["1", "2"])
|
300
|
+
RSchema.validate(schema, [1.0, 2.0]) #=> true
|
301
|
+
RSchema.validate(schema, [1, 2]) #=> false
|
302
|
+
RSchema.coerce!(schema, ["1", "2"]) #=> [1.0, 2.0]
|
245
303
|
```
|
246
304
|
|
247
305
|
The `schema_walk` method receives two arguments:
|
@@ -262,7 +320,9 @@ The `schema_walk` method has three responsibilities:
|
|
262
320
|
|
263
321
|
3. It must propagate any `RSchema::ErrorDetails` objects returned from walking
|
264
322
|
the subvalues. Walking subvalues with `RSchema.walk` may return an error,
|
265
|
-
in which case the `rschema_walk` method must also return an error.
|
323
|
+
in which case the `rschema_walk` method must also return an error. Use the
|
324
|
+
method `RSchema::ErrorDetails#extend_key_path` in this situation, to
|
325
|
+
include additional information in the error before returning it.
|
266
326
|
|
267
327
|
[Prismatic/schema]: https://github.com/Prismatic/schema
|
268
328
|
|
data/lib/rschema.rb
CHANGED
@@ -20,8 +20,8 @@ module RSchema
|
|
20
20
|
end
|
21
21
|
end
|
22
22
|
|
23
|
-
def self.schema(&block)
|
24
|
-
|
23
|
+
def self.schema(dsl=RSchema::DSL, &block)
|
24
|
+
dsl.instance_exec(&block)
|
25
25
|
end
|
26
26
|
|
27
27
|
def self.validation_error(schema, value)
|
@@ -69,37 +69,40 @@ module RSchema
|
|
69
69
|
end
|
70
70
|
|
71
71
|
module DSL
|
72
|
-
|
73
|
-
|
74
|
-
|
72
|
+
module Base
|
73
|
+
def _?(key)
|
74
|
+
OptionalHashKey.new(key)
|
75
|
+
end
|
75
76
|
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
77
|
+
def hash_of(subschemas_hash)
|
78
|
+
raise InvalidSchemaError unless subschemas_hash.size == 1
|
79
|
+
GenericHashSchema.new(subschemas_hash.keys.first, subschemas_hash.values.first)
|
80
|
+
end
|
80
81
|
|
81
|
-
|
82
|
-
|
83
|
-
|
82
|
+
def set_of(subschema)
|
83
|
+
GenericSetSchema.new(subschema)
|
84
|
+
end
|
84
85
|
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
86
|
+
def predicate(name = nil, &block)
|
87
|
+
raise InvalidSchemaError unless block
|
88
|
+
PredicateSchema.new(name, block)
|
89
|
+
end
|
89
90
|
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
91
|
+
def maybe(subschema)
|
92
|
+
raise InvalidSchemaError unless subschema
|
93
|
+
MaybeSchema.new(subschema)
|
94
|
+
end
|
94
95
|
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
96
|
+
def enum(possible_values, subschema = nil)
|
97
|
+
raise InvalidSchemaError unless possible_values && possible_values.size > 0
|
98
|
+
EnumSchema.new(Set.new(possible_values), subschema)
|
99
|
+
end
|
99
100
|
|
100
|
-
|
101
|
-
|
101
|
+
def boolean
|
102
|
+
BooleanSchema
|
103
|
+
end
|
102
104
|
end
|
105
|
+
extend Base
|
103
106
|
end
|
104
107
|
|
105
108
|
module CoercionMapper
|
@@ -222,7 +225,7 @@ module RSchema
|
|
222
225
|
if value_set.include?(value_walked)
|
223
226
|
value_walked
|
224
227
|
else
|
225
|
-
RSchema::ErrorDetails.new(value_walked, "
|
228
|
+
RSchema::ErrorDetails.new(value_walked, "is not a valid enum member")
|
226
229
|
end
|
227
230
|
end
|
228
231
|
end
|
data/lib/rschema/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rschema
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Tom Dalling
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-02-
|
11
|
+
date: 2015-02-28 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rspec
|