rschema 1.0.1 → 1.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.
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