rschema 1.0.1 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (5) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +103 -43
  3. data/lib/rschema.rb +30 -27
  4. data/lib/rschema/version.rb +1 -1
  5. metadata +2 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: f7aee8e217018b43b91e370e4e1105ec5b63f8cb
4
- data.tar.gz: 8971f79e1f327a1f614cd7eab636cb19cf306e86
3
+ metadata.gz: cb8c9bd48727609ab023125d1e36c302db187f18
4
+ data.tar.gz: f78899c23ad12c9b089b8789a3c5b1cfd36daf20
5
5
  SHA512:
6
- metadata.gz: cc8baa1863f2e8b08581af74a1df9bd92bc42f4bba9acfd33a48ad677737eec92e9fb2a355c95eace2928eb5924c25d62ded4ef99da58183aae52a4c36cc5e19
7
- data.tar.gz: c63890adc4ec24ee62ee601a150b78f8e50a88b620a74a46586060b14c01c0d640d00aa6f4da5889d2316c87baf3d144ac4a47bb8abf56b83b21f083b8ada3c2
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!(post_schema, {
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
- }) # ok
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!(schema, 5) # ok
39
- RSchema.validate!(schema, 'hello') # !!! raises RSchema::ValidationError !!!
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!(schema, [10, 11, 12]) # ok
48
- RSchema.validate!(schema, [10, 11, '12']) # !!! raises RSchema::ValidationError !!!
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!(schema, { fname: 'Jane', age: 27 }) # ok
56
- RSchema.validate!(schema, { fname: 'Johnny' }) # !!! raises RSchema::ValidationError !!!
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!(schema, {
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
- }) # ok
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!(schema, [:a, :b, :c]) # ok
88
- RSchema.validate!(schema, [:a]) # ok
89
- RSchema.validate!(schema, []) # ok
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!(schema, [10, 'hello']) # ok
97
- RSchema.validate!(schema, [10, 'hello', 'world']) # !!! raises RSchema::ValidationError !!!
98
- RSchema.validate!(schema, [10]) # !!! raises RSchema::ValidationError !!!
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!(schema, { fname: 'William' }) # ok
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!(schema, { fname: 'Lucy', age: 21 }) # ok
119
- RSchema.validate!(schema, { fname: 'Tom' }) # ok
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!(schema, { 'hello' => 1, 'world' => 2 }) # ok
128
- RSchema.validate!(schema, { 'hello' => 1 }) # ok
129
- RSchema.validate!(schema, {}) # ok
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!(boolean_schema, false) # ok
141
- RSchema.validate!(boolean_schema, nil) # !!! raises RSchema::ValidationError !!!
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!(maybe_schema, 5) # ok
146
- RSchema.validate!(maybe_schema, nil) # ok
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!(enum_schema, :a) # ok
151
- RSchema.validate!(enum_schema, :z) # !!! raises RSchema::ValidationError !!!
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!(predicate_schema, 4) # ok
158
- RSchema.validate!(predicate_schema, 5) # !!! raises RSchema::ValidationError !!!
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
- The RSchema DSL can be extended by adding methods to the `RSchema::DSL` module:
244
+ You can create new, custom DSLs that extend the default DSL like so:
192
245
 
193
246
  ```ruby
194
- module RSchema::DSL
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
- schema = RSchema.schema { positive_and_even }
201
- RSchema.validate!(schema, 6) # ok
202
- RSchema.validate!(schema, -6) # !!! raises RSchema::ValidationError !!!
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 RSchema::ErrorDetails.new({ x: x_error.details }) if x_error
226
- return RSchema::ErrorDetails.new({ y: y_error.details }) if y_error
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!(schema, [1.0, 2.0]) # ok
243
- RSchema.validate!(schema, [1, 2]) # !!! raises RSchema::ValidationError !!!
244
- RSchema.coerce!(schema, ["1", "2"]) #=> [1.0, 2.0]
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
- DSL.instance_exec(&block)
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
- def self._?(key)
73
- OptionalHashKey.new(key)
74
- end
72
+ module Base
73
+ def _?(key)
74
+ OptionalHashKey.new(key)
75
+ end
75
76
 
76
- def self.hash_of(subschemas_hash)
77
- raise InvalidSchemaError unless subschemas_hash.size == 1
78
- GenericHashSchema.new(subschemas_hash.keys.first, subschemas_hash.values.first)
79
- end
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
- def self.set_of(subschema)
82
- GenericSetSchema.new(subschema)
83
- end
82
+ def set_of(subschema)
83
+ GenericSetSchema.new(subschema)
84
+ end
84
85
 
85
- def self.predicate(name = nil, &block)
86
- raise InvalidSchemaError unless block
87
- PredicateSchema.new(name, block)
88
- end
86
+ def predicate(name = nil, &block)
87
+ raise InvalidSchemaError unless block
88
+ PredicateSchema.new(name, block)
89
+ end
89
90
 
90
- def self.maybe(subschema)
91
- raise InvalidSchemaError unless subschema
92
- MaybeSchema.new(subschema)
93
- end
91
+ def maybe(subschema)
92
+ raise InvalidSchemaError unless subschema
93
+ MaybeSchema.new(subschema)
94
+ end
94
95
 
95
- def self.enum(possible_values, subschema = nil)
96
- raise InvalidSchemaError unless possible_values && possible_values.size > 0
97
- EnumSchema.new(Set.new(possible_values), subschema)
98
- end
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
- def self.boolean
101
- BooleanSchema
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, "#{value_walked} is not a valid enum member")
228
+ RSchema::ErrorDetails.new(value_walked, "is not a valid enum member")
226
229
  end
227
230
  end
228
231
  end
@@ -1,3 +1,3 @@
1
1
  module RSchema
2
- VERSION = '1.0.1'
2
+ VERSION = '1.1.0'
3
3
  end
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.1
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-26 00:00:00.000000000 Z
11
+ date: 2015-02-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rspec