rschema 3.0.1.pre3 → 3.0.1.pre4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +278 -330
  3. data/lib/rschema.rb +104 -17
  4. data/lib/rschema/coercers.rb +3 -0
  5. data/lib/rschema/coercers/any.rb +40 -0
  6. data/lib/rschema/coercers/boolean.rb +30 -0
  7. data/lib/rschema/coercers/chain.rb +41 -0
  8. data/lib/rschema/coercers/date.rb +25 -0
  9. data/lib/rschema/coercers/fixed_hash/default_booleans_to_false.rb +62 -0
  10. data/lib/rschema/coercers/fixed_hash/remove_extraneous_attributes.rb +42 -0
  11. data/lib/rschema/coercers/fixed_hash/symbolize_keys.rb +62 -0
  12. data/lib/rschema/coercers/float.rb +18 -0
  13. data/lib/rschema/coercers/integer.rb +18 -0
  14. data/lib/rschema/coercers/symbol.rb +21 -0
  15. data/lib/rschema/coercers/time.rb +25 -0
  16. data/lib/rschema/coercion_wrapper.rb +46 -0
  17. data/lib/rschema/coercion_wrapper/rack_params.rb +21 -0
  18. data/lib/rschema/dsl.rb +271 -42
  19. data/lib/rschema/error.rb +12 -30
  20. data/lib/rschema/options.rb +2 -2
  21. data/lib/rschema/result.rb +18 -4
  22. data/lib/rschema/schemas.rb +3 -0
  23. data/lib/rschema/schemas/anything.rb +14 -12
  24. data/lib/rschema/schemas/boolean.rb +20 -21
  25. data/lib/rschema/schemas/coercer.rb +37 -0
  26. data/lib/rschema/schemas/convenience.rb +53 -0
  27. data/lib/rschema/schemas/enum.rb +25 -25
  28. data/lib/rschema/schemas/fixed_hash.rb +110 -91
  29. data/lib/rschema/schemas/fixed_length_array.rb +48 -48
  30. data/lib/rschema/schemas/maybe.rb +18 -17
  31. data/lib/rschema/schemas/pipeline.rb +20 -19
  32. data/lib/rschema/schemas/predicate.rb +24 -21
  33. data/lib/rschema/schemas/set.rb +40 -45
  34. data/lib/rschema/schemas/sum.rb +24 -28
  35. data/lib/rschema/schemas/type.rb +22 -21
  36. data/lib/rschema/schemas/variable_hash.rb +53 -52
  37. data/lib/rschema/schemas/variable_length_array.rb +39 -38
  38. data/lib/rschema/version.rb +1 -1
  39. metadata +49 -5
  40. data/lib/rschema/http_coercer.rb +0 -218
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 69d97f3f15a446af8d934668d8dcaf984ebd5ff3
4
- data.tar.gz: 8894e813dea20eaa3d54437f59517dc85832612e
3
+ metadata.gz: 039f7db20e1b5e4106983ee4be9a6fa9ec5b3686
4
+ data.tar.gz: 63ab77eaa9563d038e241c93bfa3a19a038d61c1
5
5
  SHA512:
6
- metadata.gz: e188cfabcd694d114e4af241e21bea0573506dd6ae955fed3ce0a886048e1b6c567feaae8170a9ae59276c7712233381092abbe97ca8f23b115ffd8b94a4ace4
7
- data.tar.gz: 84f09e88cd553e2f8d19d1f5ecc11ac9e31be2a17acd4838c49ef4e508624b72818bd98f8129d925ad7180fb66d6874391715e002c318c6c79892db4cca0b073
6
+ metadata.gz: 94b58792f9148118644af0a2aaa18f641cbf33063a8ed9e167372741a6ccb62483d77c46e695c1121c6bfbd8b26e16800ec34650fcf6dac4fd90e9b6900e865f
7
+ data.tar.gz: 1f60e46c2d4fcecf208617fe651c5c278e4bb1f7449fcde7208a75df3e37c23d8443f0bc8ffc7f676dd3d0e063f3feb78842273762b95140f0666283ad0a8323
data/README.md CHANGED
@@ -1,11 +1,17 @@
1
1
  [![Build Status](https://travis-ci.org/tomdalling/rschema.svg?branch=master)](https://travis-ci.org/tomdalling/rschema)
2
2
  [![Test Coverage](https://codeclimate.com/github/tomdalling/rschema/badges/coverage.svg)](https://codeclimate.com/github/tomdalling/rschema/coverage)
3
3
 
4
+ NOTE: These are the docs for the version 3 prerelease
5
+ =====================================================
6
+
7
+ For earlier versions, see the tags.
8
+
9
+
4
10
  RSchema
5
11
  =======
6
12
 
7
13
  Schema-based validation and coercion for Ruby data structures. Heavily inspired
8
- by [Prismatic/schema][].
14
+ by [Prismatic/schema](https://github.com/Prismatic/schema).
9
15
 
10
16
  Meet RSchema
11
17
  ------------
@@ -14,82 +20,72 @@ RSchema provides a way to describe, validate, and coerce the "shape" of data.
14
20
 
15
21
  First you create a schema:
16
22
 
17
- ```ruby
18
- blog_post_schema = RSchema.define_hash {{
19
- title: _String,
20
- tags: Array(_Symbol),
21
- body: _String,
22
- }}
23
- ```
23
+ blog_post_schema = RSchema.define_hash {{
24
+ title: _String,
25
+ tags: Array(_Symbol),
26
+ body: _String,
27
+ }}
24
28
 
25
29
  Then you can use the schema to validate data:
26
30
 
27
- ```ruby
28
- input = {
29
- title: "One Weird Trick Developers Don't Want You To Know!",
30
- tags: [:trick, :developers, :unbeleivable],
31
- body: '<p>blah blah</p>'
32
- }
33
- blog_post_schema.call(input).valid? #=> true
34
- ```
31
+ input = {
32
+ title: "One Weird Trick Developers Don't Want You To Know!",
33
+ tags: [:trick, :developers, :unbeleivable],
34
+ body: '<p>blah blah</p>'
35
+ }
36
+ blog_post_schema.call(input).valid? #=> true
35
37
 
36
38
  What Is A Schema?
37
39
  -----------------
38
40
 
39
- Schemas are objects that _describe and validate a values_.
41
+ Schemas are objects that _describe and validate a value_.
40
42
 
41
43
  The simplest schemas are `Type` schemas, which just validate the type of a value.
42
44
 
43
- ```ruby
44
- schema = RSchema.define { _Integer }
45
- schema.class #=> RSchema::Schemas::Type
45
+ schema = RSchema.define { _Integer }
46
+ schema.class #=> RSchema::Schemas::Type
46
47
 
47
- schema.call(1234).valid? #=> true
48
- schema.call('hi').valid? #=> false
49
- ```
48
+ schema.call(1234).valid? #=> true
49
+ schema.call('hi').valid? #=> false
50
50
 
51
51
  Then there are composite schemas, which are schemas composed of subschemas.
52
52
 
53
53
  Arrays are composite schemas:
54
54
 
55
- ```ruby
56
- schema = RSchema.define { Array(_Integer) }
57
- schema.call([10, 11, 12]).valid? #=> true
58
- schema.call([10, 11, :hi]).valid? #=> false
59
- ```
55
+ schema = RSchema.define { Array(_Integer) }
56
+ schema.call([10, 11, 12]).valid? #=> true
57
+ schema.call([10, 11, :hi]).valid? #=> false
60
58
 
61
59
  And so are hashes:
62
60
 
63
- ```ruby
64
- schema = RSchema.define do
65
- Hash(fname: _String, age: _Integer)
66
- end
61
+ schema = RSchema.define do
62
+ Hash(fname: _String, age: _Integer)
63
+ end
67
64
 
68
- schema.call({ fname: 'Jane', age: 27 }).valid? #=> true
69
- schema.call({ fname: 'Johnny' }).valid? #=> false
70
- ```
65
+ schema.call({ fname: 'Jane', age: 27 }).valid? #=> true
66
+ schema.call({ fname: 'Johnny' }).valid? #=> false
71
67
 
72
68
  Schema objects are composable – they are designed to be combined.
73
69
  This allows schemas to describe complex, nested data structures.
74
70
 
75
- ```ruby
76
- schema = RSchema.define_hash {{
77
- fname: predicate { |n| n.is_a?(String) && n.size > 0 },
78
- favourite_foods: Set(_Symbol),
79
- children_by_age: VariableHash(_Integer => _String)
80
- }}
81
-
82
- input = {
83
- fname: 'Johnny',
84
- favourite_foods: Set.new([:bacon, :cheese, :onion]),
85
- children_by_age: {
86
- 7 => 'Jenny',
87
- 5 => 'Simon',
88
- },
89
- }
90
-
91
- schema.call(input).valid? #=> true
92
- ```
71
+ schema = RSchema.define_hash do
72
+ {
73
+ fname: predicate { |n| n.is_a?(String) && n.size > 0 },
74
+ favourite_foods: Set(_Symbol),
75
+ children_by_age: VariableHash(_Integer => _String)
76
+ }
77
+ end
78
+
79
+ input = {
80
+ fname: 'Johnny',
81
+ favourite_foods: Set.new([:bacon, :cheese, :onion]),
82
+ children_by_age: {
83
+ 7 => 'Jenny',
84
+ 5 => 'Simon',
85
+ },
86
+ }
87
+
88
+ schema.call(input).valid? #=> true
93
89
 
94
90
  RSchema provides many different kinds of schema classes for common tasks, but
95
91
  you can also write custom schema classes if you need to.
@@ -104,13 +100,11 @@ They can be created manually, although this is often too verbose.
104
100
  For example, the following two schemas are identical. `schema1` is created via the
105
101
  DSL, and `schema2` is created manually.
106
102
 
107
- ```ruby
108
- schema1 = RSchema.define { Array(_Symbol) }
103
+ schema1 = RSchema.define { Array(_Symbol) }
109
104
 
110
- schema2 = RSchema::Schemas::VariableArray.new(
111
- RSchema::Schemas::Type.new(Symbol)
112
- )
113
- ```
105
+ schema2 = RSchema::Schemas::VariableArray.new(
106
+ RSchema::Schemas::Type.new(Symbol)
107
+ )
114
108
 
115
109
  You will probably never need to create schemas manually unless you are doing
116
110
  something advanced, like writing your own DSL.
@@ -126,31 +120,29 @@ When data fails validation, it is often important to know exactly _which
126
120
  values_ were invalid, and _why_. RSchema provides details about every
127
121
  failure within a result object.
128
122
 
129
- ```ruby
130
- schema = RSchema.define do
131
- Array(
132
- Hash(
133
- name: _String,
134
- hair: enum([:red, :brown, :blonde, :black])
135
- )
136
- )
137
- end
123
+ schema = RSchema.define do
124
+ Array(
125
+ Hash(
126
+ name: _String,
127
+ hair: enum([:red, :brown, :blonde, :black])
128
+ )
129
+ )
130
+ end
138
131
 
139
- input = [
140
- { name: 'Dane', hair: :black },
141
- { name: 'Tom', hair: :brown },
142
- { name: 'Effie', hair: :blond },
143
- { name: 'Chris', hair: :red },
144
- ]
132
+ input = [
133
+ { name: 'Dane', hair: :black },
134
+ { name: 'Tom', hair: :brown },
135
+ { name: 'Effie', hair: :blond },
136
+ { name: 'Chris', hair: :red },
137
+ ]
145
138
 
146
- result = schema.call(input)
139
+ result = schema.call(input)
147
140
 
148
- result.class #=> RSchema::Result
149
- result.valid? #=> false
150
- result.error #=> { 2 => { :hair => #<RSchema::Error> } }
151
- result.error[2][:hair].to_s
152
- #=> "Error RSchema::Schemas::Enum/not_a_member for value: :blond"
153
- ```
141
+ result.class #=> RSchema::Result
142
+ result.valid? #=> false
143
+ result.error #=> { 2 => { :hair => #<RSchema::Error> } }
144
+ result.error[2][:hair].to_s
145
+ #=> "Error RSchema::Schemas::Enum/not_a_member for value: :blond"
154
146
 
155
147
  The error above says that the value `:blond`, which exists at location
156
148
  `input[2][:hair]`, is not a valid enum member. Looking back at the schema, we
@@ -159,20 +151,19 @@ see that there is a typo, and it should be `:blonde` instead of `:blond`.
159
151
  Error objects contain a lot of information, which can be used to generate
160
152
  error messages for developers or users.
161
153
 
162
- ```ruby
163
- error = result.error[2][:hair]
164
- error.class #=> RSchema::Error
165
-
166
- error.value #=> :blond
167
- error.symbolic_name #=> :not_a_member
168
- error.schema #=> #<RSchema::Schemas::Enum>
169
- error.to_s #=> "Error RSchema::Schemas::Enum/not_a_member for value: :blond"
170
- error.to_s(:detailed) #=>
171
- # Error: not_a_member
172
- # Schema: RSchema::Schemas::Enum
173
- # Value: :blond
174
- # Vars: nil
175
- ```
154
+ error = result.error[2][:hair]
155
+ error.class #=> RSchema::Error
156
+
157
+ error.value #=> :blond
158
+ error.symbolic_name #=> :not_a_member
159
+ error.schema #=> #<RSchema::Schemas::Enum>
160
+ error.to_s #=> "Error RSchema::Schemas::Enum/not_a_member for value: :blond"
161
+ error.to_s(:detailed) #=>
162
+ # Error: not_a_member
163
+ # Schema: RSchema::Schemas::Enum
164
+ # Value: :blond
165
+ # Vars: nil
166
+
176
167
 
177
168
  Type Schemas
178
169
  ------------
@@ -180,29 +171,23 @@ Type Schemas
180
171
  The most basic kind of schema is a `Type` schema.
181
172
  Type schemas validate the class of a value using `is_a?`.
182
173
 
183
- ```ruby
184
- schema = RSchema.define { type(String) }
185
- schema.call('hi').valid? #=> true
186
- schema.call(1234).valid? #=> false
187
- ```
174
+ schema = RSchema.define { type(String) }
175
+ schema.call('hi').valid? #=> true
176
+ schema.call(1234).valid? #=> false
188
177
 
189
178
  Type schemas are so common that the RSchema DSL provides a shorthand way to
190
179
  create them, using an underscore prefix:
191
180
 
192
- ```ruby
193
- schema1 = RSchema.define { _Integer }
194
- # is exactly the same as
195
- schema2 = RSchema.define { type(Integer) }
196
- ```
181
+ schema1 = RSchema.define { _Integer }
182
+ # is exactly the same as
183
+ schema2 = RSchema.define { type(Integer) }
197
184
 
198
185
  Because type schemas use `is_a?`, they handle subclasses, and can also be used
199
186
  to check for `include`d modules like `Enumerable`:
200
187
 
201
- ```ruby
202
- schema = RSchema.define { _Enumerable }
203
- schema.call([1, 2, 3]).valid? #=> true
204
- schema.call({ a: 1, b: 2 }).valid? #=> true
205
- ```
188
+ schema = RSchema.define { _Enumerable }
189
+ schema.call([1, 2, 3]).valid? #=> true
190
+ schema.call({ a: 1, b: 2 }).valid? #=> true
206
191
 
207
192
  Variable-length Array Schemas
208
193
  -----------------------------
@@ -211,14 +196,12 @@ There are two types of array schemas.
211
196
  The first type are `VariableLengthArray` schemas, where every element in the
212
197
  array conforms to a single subschema:
213
198
 
214
- ```ruby
215
- schema = RSchema.define { Array(_Symbol) }
216
- schema.class #=> RSchema::Schemas::VariableLengthArray
199
+ schema = RSchema.define { Array(_Symbol) }
200
+ schema.class #=> RSchema::Schemas::VariableLengthArray
217
201
 
218
- schema.call([:a, :b, :c]).valid? #=> true
219
- schema.call([:a]).valid? #=> true
220
- schema.call([]).valid? #=> true
221
- ```
202
+ schema.call([:a, :b, :c]).valid? #=> true
203
+ schema.call([:a]).valid? #=> true
204
+ schema.call([]).valid? #=> true
222
205
 
223
206
  Fixed-length Array Schemas
224
207
  --------------------------
@@ -226,14 +209,12 @@ Fixed-length Array Schemas
226
209
  There are also `FixedLengthArray` schemas, where the array must have a specific
227
210
  length, and each element of the array has a separate subschema:
228
211
 
229
- ```ruby
230
- schema = RSchema.define{ Array(_Integer, _String) }
231
- schema.class #=> RSchema::Schemas::FixedLengthArray
212
+ schema = RSchema.define{ Array(_Integer, _String) }
213
+ schema.class #=> RSchema::Schemas::FixedLengthArray
232
214
 
233
- schema.call([10, 'hello']).valid? #=> true
234
- schema.call([22, 'world']).valid? #=> true
235
- schema.call(['heyoo', 33]).valid? #=> false
236
- ```
215
+ schema.call([10, 'hello']).valid? #=> true
216
+ schema.call([22, 'world']).valid? #=> true
217
+ schema.call(['heyoo', 33]).valid? #=> false
237
218
 
238
219
  Fixed Hash Schemas
239
220
  ------------------
@@ -242,37 +223,31 @@ There are also two kinds of hash schemas.
242
223
 
243
224
  `FixedHash` schemas describes hashes where they keys are known constants:
244
225
 
245
- ```ruby
246
- schema = RSchema.define do
247
- Hash(name: _String, age: _Integer)
248
- end
226
+ schema = RSchema.define do
227
+ Hash(name: _String, age: _Integer)
228
+ end
249
229
 
250
- schema.call({ name: 'George', age: 2 }).valid? #=> true
251
- ```
230
+ schema.call({ name: 'George', age: 2 }).valid? #=> true
252
231
 
253
232
  Elements can be optional:
254
233
 
255
- ```ruby
256
- schema = RSchema.define do
257
- Hash(
258
- name: _String,
259
- optional(:age) => _Integer,
260
- )
261
- end
234
+ schema = RSchema.define do
235
+ Hash(
236
+ name: _String,
237
+ optional(:age) => _Integer,
238
+ )
239
+ end
262
240
 
263
- schema.call({ name: 'Lucy', age: 21 }).valid? #=> true
264
- schema.call({ name: 'Tom' }).valid? #=> true
265
- ```
241
+ schema.call({ name: 'Lucy', age: 21 }).valid? #=> true
242
+ schema.call({ name: 'Tom' }).valid? #=> true
266
243
 
267
244
  `FixedHash` schemas are common, so the `RSchema.define_hash` method exists
268
245
  to make their creation more convenient:
269
246
 
270
- ```ruby
271
- schema = RSchema.define_hash {{
272
- name: _String,
273
- optional(:age) => _Integer,
274
- }}
275
- ```
247
+ schema = RSchema.define_hash {{
248
+ name: _String,
249
+ optional(:age) => _Integer,
250
+ }}
276
251
 
277
252
  Variable Hash Schemas
278
253
  ---------------------
@@ -280,66 +255,62 @@ Variable Hash Schemas
280
255
  `VariableHash` schemas are for hashes where the keys are _not_ known constants.
281
256
  They contain one subschema for keys, and another subschema for values.
282
257
 
283
- ```ruby
284
- schema = RSchema.define { VariableHash(_Symbol => _Integer) }
285
- schema.call({}).valid? #=> true
286
- schema.call({ a: 1 }).valid? #=> true
287
- schema.call({ a: 1, b: 2 }).valid? #=> true
288
- ```
258
+ schema = RSchema.define { VariableHash(_Symbol => _Integer) }
259
+ schema.call({}).valid? #=> true
260
+ schema.call({ a: 1 }).valid? #=> true
261
+ schema.call({ a: 1, b: 2 }).valid? #=> true
289
262
 
290
263
  Other Schema Types
291
264
  ------------------
292
265
 
293
266
  RSchema provides a few other schema types through its DSL:
294
267
 
295
- ```ruby
296
- # boolean (only true or false)
297
- boolean_schema = RSchema.define { Boolean() }
298
- boolean_schema.call(true).valid? #=> true
299
- boolean_schema.call(false).valid? #=> true
300
- boolean_schema.call(nil).valid? #=> false
301
-
302
- # anything (literally any value)
303
- anything_schema = RSchema.define { anything }
304
- anything_schema.call('Hi').valid? #=> true
305
- anything_schema.call(true).valid? #=> true
306
- anything_schema.call(1234).valid? #=> true
307
- anything_schema.call(nil).valid? #=> true
308
-
309
- # either (sum types)
310
- either_schema = RSchema.define { either(_String, _Integer, _Float) }
311
- either_schema.call('hi').valid? #=> true
312
- either_schema.call(5555).valid? #=> true
313
- either_schema.call(77.1).valid? #=> true
314
-
315
- # maybe (allows nil)
316
- maybe_schema = RSchema.define { maybe(_Integer) }
317
- maybe_schema.call(5).valid? #=> true
318
- maybe_schema.call(nil).valid? #=> true
319
-
320
- # enum (a set of valid values)
321
- enum_schema = RSchema.define { enum([:a, :b, :c]) }
322
- enum_schema.call(:a).valid? #=> true
323
- enum_schema.call(:z).valid? #=> false
324
-
325
- # predicate (block returns true for valid values)
326
- predicate_schema = RSchema.define do
327
- predicate { |x| x.even? }
328
- end
329
- predicate_schema.call(4).valid? #=> true
330
- predicate_schema.call(5).valid? #=> false
331
-
332
- # pipeline (apply multiple schemas to a single value, in order)
333
- pipeline_schema = RSchema.define do
334
- pipeline(
335
- either(_Integer, _Float),
336
- predicate { |x| x.positive? },
337
- )
338
- end
339
- pipeline_schema.call(123).valid? #=> true
340
- pipeline_schema.call(5.1).valid? #=> true
341
- pipeline_schema.call(-24).valid? #=> false
342
- ```
268
+ # boolean (only true or false)
269
+ boolean_schema = RSchema.define { Boolean() }
270
+ boolean_schema.call(true).valid? #=> true
271
+ boolean_schema.call(false).valid? #=> true
272
+ boolean_schema.call(nil).valid? #=> false
273
+
274
+ # anything (literally any value)
275
+ anything_schema = RSchema.define { anything }
276
+ anything_schema.call('Hi').valid? #=> true
277
+ anything_schema.call(true).valid? #=> true
278
+ anything_schema.call(1234).valid? #=> true
279
+ anything_schema.call(nil).valid? #=> true
280
+
281
+ # either (sum types)
282
+ either_schema = RSchema.define { either(_String, _Integer, _Float) }
283
+ either_schema.call('hi').valid? #=> true
284
+ either_schema.call(5555).valid? #=> true
285
+ either_schema.call(77.1).valid? #=> true
286
+
287
+ # maybe (allows nil)
288
+ maybe_schema = RSchema.define { maybe(_Integer) }
289
+ maybe_schema.call(5).valid? #=> true
290
+ maybe_schema.call(nil).valid? #=> true
291
+
292
+ # enum (a set of valid values)
293
+ enum_schema = RSchema.define { enum([:a, :b, :c]) }
294
+ enum_schema.call(:a).valid? #=> true
295
+ enum_schema.call(:z).valid? #=> false
296
+
297
+ # predicate (block returns true for valid values)
298
+ predicate_schema = RSchema.define do
299
+ predicate { |x| x.even? }
300
+ end
301
+ predicate_schema.call(4).valid? #=> true
302
+ predicate_schema.call(5).valid? #=> false
303
+
304
+ # pipeline (apply multiple schemas to a single value, in order)
305
+ pipeline_schema = RSchema.define do
306
+ pipeline(
307
+ either(_Integer, _Float),
308
+ predicate { |x| x.positive? },
309
+ )
310
+ end
311
+ pipeline_schema.call(123).valid? #=> true
312
+ pipeline_schema.call(5.1).valid? #=> true
313
+ pipeline_schema.call(-24).valid? #=> false
343
314
 
344
315
 
345
316
  Coercion
@@ -354,29 +325,27 @@ are often expected to be `Symbol`s, but are also strings. The `HTTPCoercer`
354
325
  can automatically convert these strings into the appropriate type, based on a
355
326
  schema.
356
327
 
357
- ```ruby
358
- # Input keys and values are all strings.
359
- input_params = {
360
- 'whatever_id' => '5',
361
- 'amount' => '123.45',
362
- }
328
+ # Input keys and values are all strings.
329
+ input_params = {
330
+ 'whatever_id' => '5',
331
+ 'amount' => '123.45',
332
+ }
363
333
 
364
- # The schema expects symbol keys, an integer value, and a float value.
365
- param_schema = RSchema.define_hash {{
366
- whatever_id: _Integer,
367
- amount: _Float,
368
- }}
334
+ # The schema expects symbol keys, an integer value, and a float value.
335
+ param_schema = RSchema.define_hash {{
336
+ whatever_id: _Integer,
337
+ amount: _Float,
338
+ }}
369
339
 
370
- # The schema is wrapped in a HTTPCoercer.
371
- coercer = RSchema::HTTPCoercer.wrap(param_schema)
340
+ # The schema is wrapped in a HTTPCoercer.
341
+ coercer = RSchema::HTTPCoercer.wrap(param_schema)
372
342
 
373
- # Use the coercer like a normal schema object.
374
- result = coercer.call(input_params)
343
+ # Use the coercer like a normal schema object.
344
+ result = coercer.call(input_params)
375
345
 
376
- # The result object contains the coerced value
377
- result.valid? #=> true
378
- result.value #=> { :whatever_id => 5, :amount => 123.45 }
379
- ```
346
+ # The result object contains the coerced value
347
+ result.valid? #=> true
348
+ result.value #=> { :whatever_id => 5, :amount => 123.45 }
380
349
 
381
350
  TODO: explain how to create custom coercers
382
351
 
@@ -385,31 +354,25 @@ Extending The DSL
385
354
 
386
355
  To add methods to the default DSL, first create a module:
387
356
 
388
- ```ruby
389
- module MyCustomMethods
390
- def palendrome
391
- pipeline(
392
- _String,
393
- predicate { |s| s == s.reverse },
394
- )
395
- end
396
- end
397
- ```
357
+ module MyCustomMethods
358
+ def palendrome
359
+ pipeline(
360
+ _String,
361
+ predicate { |s| s == s.reverse },
362
+ )
363
+ end
364
+ end
398
365
 
399
366
  Then include your module into `RSchema::DefaultDSL`:
400
367
 
401
- ```ruby
402
- RSchema::DefaultDSL.include(MyCustomMethods)
403
- ```
368
+ RSchema::DefaultDSL.include(MyCustomMethods)
404
369
 
405
370
  And your methods will be available via `RSchema.define`:
406
371
 
407
- ```ruby
408
- schema = RSchema.define { palendrome }
372
+ schema = RSchema.define { palendrome }
409
373
 
410
- schema.call('racecar').valid? #=> true
411
- schema.call('ferrari').valid? #=> false
412
- ```
374
+ schema.call('racecar').valid? #=> true
375
+ schema.call('ferrari').valid? #=> false
413
376
 
414
377
  This is the preferred way for other gems to extend RSchema with new kinds
415
378
  of schema classes.
@@ -426,25 +389,21 @@ Create a new class, and include `RSchema::DSL` to get all the standard DSL
426
389
  methods that come built-in to RSchema. You can define your own custom methods
427
390
  on this class.
428
391
 
429
- ```ruby
430
- class MyCustomDSL
431
- include RSchema::DSL
392
+ class MyCustomDSL
393
+ include RSchema::DSL
432
394
 
433
- def palendrome
434
- pipeline(
435
- _String,
436
- predicate { |s| s == s.reverse },
437
- )
438
- end
439
- end
440
- ```
395
+ def palendrome
396
+ pipeline(
397
+ _String,
398
+ predicate { |s| s == s.reverse },
399
+ )
400
+ end
401
+ end
441
402
 
442
403
  Then simply use `instance_eval` to make use of your custom DSL.
443
404
 
444
- ```ruby
445
- schema = MyCustomDSL.new.instance_eval { palendrome }
446
- schema.call('racecar').valid? #=> true
447
- ```
405
+ schema = MyCustomDSL.new.instance_eval { palendrome }
406
+ schema.call('racecar').valid? #=> true
448
407
 
449
408
  See the implementation of `RSchema.define` for reference.
450
409
 
@@ -459,96 +418,85 @@ Below is a custom schema for pairs – arrays with two elements of the same type
459
418
  This is already possible using existing schemas (e.g. `Array(_String, _String)`),
460
419
  and is only shown here for the purpose of demonstration.
461
420
 
462
- ```ruby
463
- class PairSchema
464
- def initialize(subschema)
465
- @subschema = subschema
466
- end
467
-
468
- def call(pair, options=RSchema::Options.default)
469
- return not_an_array_failure(pair) unless pair.is_a?(Array)
470
- return not_a_pair_failure(pair) unless pair.size == 2
471
-
472
- subresults = pair.map { |x| @subschema.call(x, options) }
473
-
474
- if subresults.all?(&:valid?)
475
- RSchema::Result.success(subresults.map(&:value).to_a)
476
- else
477
- RSchema::Result.failure(subschema_error(subresults))
421
+ class PairSchema
422
+ def initialize(subschema)
423
+ @subschema = subschema
424
+ end
425
+
426
+ def call(pair, options=RSchema::Options.default)
427
+ return not_an_array_failure(pair) unless pair.is_a?(Array)
428
+ return not_a_pair_failure(pair) unless pair.size == 2
429
+
430
+ subresults = pair.map { |x| @subschema.call(x, options) }
431
+
432
+ if subresults.all?(&:valid?)
433
+ RSchema::Result.success(subresults.map(&:value).to_a)
434
+ else
435
+ RSchema::Result.failure(subschema_error(subresults))
436
+ end
437
+ end
438
+
439
+ def with_wrapped_subschemas(wrapper)
440
+ PairSchema.new(wrapper.wrap(@subschema))
441
+ end
442
+
443
+ private
444
+
445
+ def not_an_array_failure(pair)
446
+ RSchema::Result.failure(
447
+ RSchema::Error.new(
448
+ symbolic_name: :not_an_array,
449
+ schema: self,
450
+ value: pair,
451
+ )
452
+ )
453
+ end
454
+
455
+ def not_a_pair_failure(pair)
456
+ RSchema::Result.failure(
457
+ RSchema::Error.new(
458
+ symbolic_name: :not_a_pair,
459
+ schema: self,
460
+ value: pair,
461
+ vars: {
462
+ expected_size: 2,
463
+ actual_size: pair.size,
464
+ }
465
+ )
466
+ )
467
+ end
468
+
469
+ def subschema_error(subresults)
470
+ subresults
471
+ .each_with_index
472
+ .select { |(result, idx)| result.invalid? }
473
+ .map(&:reverse)
474
+ .to_h
475
+ end
478
476
  end
479
- end
480
-
481
- def with_wrapped_subschemas(wrapper)
482
- PairSchema.new(wrapper.wrap(@subschema))
483
- end
484
-
485
- private
486
-
487
- def not_an_array_failure(pair)
488
- RSchema::Result.failure(
489
- RSchema::Error.new(
490
- symbolic_name: :not_an_array,
491
- schema: self,
492
- value: pair,
493
- )
494
- )
495
- end
496
-
497
- def not_a_pair_failure(pair)
498
- RSchema::Result.failure(
499
- RSchema::Error.new(
500
- symbolic_name: :not_a_pair,
501
- schema: self,
502
- value: pair,
503
- vars: {
504
- expected_size: 2,
505
- actual_size: pair.size,
506
- }
507
- )
508
- )
509
- end
510
-
511
- def subschema_error(subresults)
512
- subresults
513
- .each_with_index
514
- .select { |(result, idx)| result.invalid? }
515
- .map(&:reverse)
516
- .to_h
517
- end
518
- end
519
- ```
520
477
 
521
478
  TODO: need to explain how to implement `#call` and `#with_wrapped_subschemas`
522
479
 
523
480
  Add your new schema class to the default DSL:
524
481
 
525
- ```ruby
526
- module PairSchemaDSL
527
- def pair(subschema)
528
- PairSchema.new(subschema)
529
- end
530
- end
482
+ module PairSchemaDSL
483
+ def pair(subschema)
484
+ PairSchema.new(subschema)
485
+ end
486
+ end
531
487
 
532
- RSchema::DefaultDSL.include(PairSchemaDSL)
533
- ```
488
+ RSchema::DefaultDSL.include(PairSchemaDSL)
534
489
 
535
490
  Then your schema is accessible from `RSchema.define`:
536
491
 
537
- ```ruby
538
- gps_coordinate_schema = RSchema.define { pair(_Float) }
539
- gps_coordinate_schema.call([1.2, 3.4]).valid? #=> true
540
- ```
492
+ gps_coordinate_schema = RSchema.define { pair(_Float) }
493
+ gps_coordinate_schema.call([1.2, 3.4]).valid? #=> true
541
494
 
542
495
  Coercion should work, as long as `#with_wrapped_subschemas` was implemented
543
496
  correctly.
544
497
 
545
- ```ruby
546
- coercer = RSchema::HTTPCoercer.wrap(gps_coordinate_schema)
547
- result = coercer.call(['1', '2'])
548
- result.valid? #=> true
549
- result.value #=> [1.0, 2.0]
550
- ```
551
-
552
-
553
- [Prismatic/schema]: https://github.com/Prismatic/schema
498
+ coercer = RSchema::HTTPCoercer.wrap(gps_coordinate_schema)
499
+ result = coercer.call(['1', '2'])
500
+ result.valid? #=> true
501
+ result.value #=> [1.0, 2.0]
554
502