rschema 3.0.1.pre3 → 3.0.1.pre4

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 (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