schemacop 2.4.7 → 3.0.0.rc0

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 (172) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +3 -0
  3. data/.rubocop.yml +25 -1
  4. data/.travis.yml +2 -1
  5. data/CHANGELOG.md +8 -0
  6. data/README.md +41 -708
  7. data/README_V2.md +775 -0
  8. data/README_V3.md +683 -0
  9. data/Rakefile +8 -12
  10. data/VERSION +1 -1
  11. data/lib/schemacop.rb +35 -37
  12. data/lib/schemacop/base_schema.rb +37 -0
  13. data/lib/schemacop/railtie.rb +10 -0
  14. data/lib/schemacop/schema.rb +1 -60
  15. data/lib/schemacop/schema2.rb +22 -0
  16. data/lib/schemacop/schema3.rb +21 -0
  17. data/lib/schemacop/scoped_env.rb +25 -13
  18. data/lib/schemacop/v2.rb +26 -0
  19. data/lib/schemacop/{caster.rb → v2/caster.rb} +16 -2
  20. data/lib/schemacop/{collector.rb → v2/collector.rb} +5 -2
  21. data/lib/schemacop/{dupper.rb → v2/dupper.rb} +1 -1
  22. data/lib/schemacop/{field_node.rb → v2/field_node.rb} +4 -3
  23. data/lib/schemacop/v2/node.rb +142 -0
  24. data/lib/schemacop/{node_resolver.rb → v2/node_resolver.rb} +1 -1
  25. data/lib/schemacop/{node_supporting_field.rb → v2/node_supporting_field.rb} +8 -10
  26. data/lib/schemacop/{node_supporting_type.rb → v2/node_supporting_type.rb} +6 -3
  27. data/lib/schemacop/{node_with_block.rb → v2/node_with_block.rb} +3 -2
  28. data/lib/schemacop/v2/root_node.rb +6 -0
  29. data/lib/schemacop/v2/validator/array_validator.rb +32 -0
  30. data/lib/schemacop/{validator → v2/validator}/boolean_validator.rb +1 -1
  31. data/lib/schemacop/v2/validator/float_validator.rb +7 -0
  32. data/lib/schemacop/v2/validator/hash_validator.rb +37 -0
  33. data/lib/schemacop/v2/validator/integer_validator.rb +7 -0
  34. data/lib/schemacop/{validator → v2/validator}/nil_validator.rb +1 -1
  35. data/lib/schemacop/v2/validator/number_validator.rb +21 -0
  36. data/lib/schemacop/v2/validator/object_validator.rb +29 -0
  37. data/lib/schemacop/v2/validator/string_validator.rb +39 -0
  38. data/lib/schemacop/{validator → v2/validator}/symbol_validator.rb +1 -1
  39. data/lib/schemacop/v3.rb +45 -0
  40. data/lib/schemacop/v3/all_of_node.rb +27 -0
  41. data/lib/schemacop/v3/any_of_node.rb +28 -0
  42. data/lib/schemacop/v3/array_node.rb +219 -0
  43. data/lib/schemacop/v3/boolean_node.rb +16 -0
  44. data/lib/schemacop/v3/combination_node.rb +45 -0
  45. data/lib/schemacop/v3/context.rb +17 -0
  46. data/lib/schemacop/v3/dsl_scope.rb +46 -0
  47. data/lib/schemacop/v3/global_context.rb +114 -0
  48. data/lib/schemacop/v3/hash_node.rb +217 -0
  49. data/lib/schemacop/v3/integer_node.rb +13 -0
  50. data/lib/schemacop/v3/is_not_node.rb +32 -0
  51. data/lib/schemacop/v3/node.rb +214 -0
  52. data/lib/schemacop/v3/node_registry.rb +49 -0
  53. data/lib/schemacop/v3/number_node.rb +18 -0
  54. data/lib/schemacop/v3/numeric_node.rb +76 -0
  55. data/lib/schemacop/v3/object_node.rb +40 -0
  56. data/lib/schemacop/v3/one_of_node.rb +28 -0
  57. data/lib/schemacop/v3/reference_node.rb +49 -0
  58. data/lib/schemacop/v3/result.rb +58 -0
  59. data/lib/schemacop/v3/string_node.rb +124 -0
  60. data/lib/schemacop/v3/symbol_node.rb +13 -0
  61. data/schemacop.gemspec +24 -27
  62. data/test/lib/test_helper.rb +152 -0
  63. data/test/schemas/nested/group.rb +6 -0
  64. data/test/schemas/user.rb +7 -0
  65. data/test/unit/schemacop/v2/casting_test.rb +120 -0
  66. data/test/unit/schemacop/v2/collector_test.rb +47 -0
  67. data/test/unit/schemacop/v2/custom_check_test.rb +95 -0
  68. data/test/unit/schemacop/v2/custom_if_test.rb +97 -0
  69. data/test/unit/schemacop/v2/defaults_test.rb +95 -0
  70. data/test/unit/schemacop/v2/empty_test.rb +16 -0
  71. data/test/unit/schemacop/v2/nil_dis_allow_test.rb +43 -0
  72. data/test/unit/schemacop/v2/node_resolver_test.rb +28 -0
  73. data/test/unit/schemacop/v2/short_forms_test.rb +351 -0
  74. data/test/unit/schemacop/v2/types_test.rb +88 -0
  75. data/test/unit/schemacop/v2/validator_array_test.rb +99 -0
  76. data/test/unit/schemacop/v2/validator_boolean_test.rb +17 -0
  77. data/test/unit/schemacop/v2/validator_float_test.rb +59 -0
  78. data/test/unit/schemacop/v2/validator_hash_test.rb +95 -0
  79. data/test/unit/schemacop/v2/validator_integer_test.rb +48 -0
  80. data/test/unit/schemacop/v2/validator_nil_test.rb +15 -0
  81. data/test/unit/schemacop/v2/validator_number_test.rb +62 -0
  82. data/test/unit/schemacop/v2/validator_object_test.rb +141 -0
  83. data/test/unit/schemacop/v2/validator_string_test.rb +78 -0
  84. data/test/unit/schemacop/v2/validator_symbol_test.rb +18 -0
  85. data/test/unit/schemacop/v3/all_of_node_test.rb +199 -0
  86. data/test/unit/schemacop/v3/any_of_node_test.rb +218 -0
  87. data/test/unit/schemacop/v3/array_node_test.rb +805 -0
  88. data/test/unit/schemacop/v3/boolean_node_test.rb +126 -0
  89. data/test/unit/schemacop/v3/global_context_test.rb +164 -0
  90. data/test/unit/schemacop/v3/hash_node_test.rb +775 -0
  91. data/test/unit/schemacop/v3/integer_node_test.rb +323 -0
  92. data/test/unit/schemacop/v3/is_not_node_test.rb +173 -0
  93. data/test/unit/schemacop/v3/node_test.rb +148 -0
  94. data/test/unit/schemacop/v3/number_node_test.rb +292 -0
  95. data/test/unit/schemacop/v3/object_node_test.rb +170 -0
  96. data/test/unit/schemacop/v3/one_of_node_test.rb +187 -0
  97. data/test/unit/schemacop/v3/reference_node_test.rb +351 -0
  98. data/test/unit/schemacop/v3/string_node_test.rb +334 -0
  99. data/test/unit/schemacop/v3/symbol_node_test.rb +75 -0
  100. metadata +152 -145
  101. data/doc/Schemacop.html +0 -146
  102. data/doc/Schemacop/ArrayValidator.html +0 -329
  103. data/doc/Schemacop/BooleanValidator.html +0 -145
  104. data/doc/Schemacop/Caster.html +0 -379
  105. data/doc/Schemacop/Collector.html +0 -787
  106. data/doc/Schemacop/Dupper.html +0 -214
  107. data/doc/Schemacop/Exceptions.html +0 -115
  108. data/doc/Schemacop/Exceptions/InvalidSchemaError.html +0 -124
  109. data/doc/Schemacop/Exceptions/ValidationError.html +0 -124
  110. data/doc/Schemacop/FieldNode.html +0 -421
  111. data/doc/Schemacop/FloatValidator.html +0 -158
  112. data/doc/Schemacop/HashValidator.html +0 -293
  113. data/doc/Schemacop/IntegerValidator.html +0 -158
  114. data/doc/Schemacop/NilValidator.html +0 -145
  115. data/doc/Schemacop/Node.html +0 -1438
  116. data/doc/Schemacop/NodeResolver.html +0 -258
  117. data/doc/Schemacop/NodeSupportingField.html +0 -590
  118. data/doc/Schemacop/NodeSupportingType.html +0 -612
  119. data/doc/Schemacop/NodeWithBlock.html +0 -289
  120. data/doc/Schemacop/NumberValidator.html +0 -232
  121. data/doc/Schemacop/ObjectValidator.html +0 -298
  122. data/doc/Schemacop/RootNode.html +0 -171
  123. data/doc/Schemacop/Schema.html +0 -699
  124. data/doc/Schemacop/StringValidator.html +0 -295
  125. data/doc/Schemacop/SymbolValidator.html +0 -145
  126. data/doc/ScopedEnv.html +0 -351
  127. data/doc/_index.html +0 -379
  128. data/doc/class_list.html +0 -51
  129. data/doc/css/common.css +0 -1
  130. data/doc/css/full_list.css +0 -58
  131. data/doc/css/style.css +0 -496
  132. data/doc/file.README.html +0 -833
  133. data/doc/file_list.html +0 -56
  134. data/doc/frames.html +0 -17
  135. data/doc/index.html +0 -833
  136. data/doc/inheritance.graphml +0 -524
  137. data/doc/inheritance.pdf +0 -825
  138. data/doc/js/app.js +0 -303
  139. data/doc/js/full_list.js +0 -216
  140. data/doc/js/jquery.js +0 -4
  141. data/doc/method_list.html +0 -587
  142. data/doc/top-level-namespace.html +0 -112
  143. data/lib/schemacop/node.rb +0 -139
  144. data/lib/schemacop/root_node.rb +0 -4
  145. data/lib/schemacop/validator/array_validator.rb +0 -30
  146. data/lib/schemacop/validator/float_validator.rb +0 -5
  147. data/lib/schemacop/validator/hash_validator.rb +0 -35
  148. data/lib/schemacop/validator/integer_validator.rb +0 -5
  149. data/lib/schemacop/validator/number_validator.rb +0 -19
  150. data/lib/schemacop/validator/object_validator.rb +0 -27
  151. data/lib/schemacop/validator/string_validator.rb +0 -37
  152. data/test/casting_test.rb +0 -118
  153. data/test/collector_test.rb +0 -45
  154. data/test/custom_check_test.rb +0 -93
  155. data/test/custom_if_test.rb +0 -95
  156. data/test/defaults_test.rb +0 -93
  157. data/test/empty_test.rb +0 -14
  158. data/test/nil_dis_allow_test.rb +0 -41
  159. data/test/node_resolver_test.rb +0 -26
  160. data/test/short_forms_test.rb +0 -349
  161. data/test/test_helper.rb +0 -13
  162. data/test/types_test.rb +0 -84
  163. data/test/validator_array_test.rb +0 -97
  164. data/test/validator_boolean_test.rb +0 -15
  165. data/test/validator_float_test.rb +0 -57
  166. data/test/validator_hash_test.rb +0 -93
  167. data/test/validator_integer_test.rb +0 -46
  168. data/test/validator_nil_test.rb +0 -13
  169. data/test/validator_number_test.rb +0 -60
  170. data/test/validator_object_test.rb +0 -139
  171. data/test/validator_string_test.rb +0 -76
  172. data/test/validator_symbol_test.rb +0 -16
@@ -0,0 +1,775 @@
1
+ # Schemacop V2 Legacy
2
+
3
+ This is the readme for the V2 legacy functionality contained in Schemacop 3. See
4
+ [the main README](README.md) for documentation of the new V3 functionality.
5
+
6
+ Schemacop validates ruby structures consisting of nested hashes and arrays
7
+ against schema definitions described by a simple DSL.
8
+
9
+ Examples:
10
+
11
+ ```ruby
12
+ schema = Schema.new do
13
+ req :naming, :hash do
14
+ opt :first_name, :string
15
+ req :last_name, :string
16
+ end
17
+ opt! :age, :integer, min: 18
18
+ req? :password do
19
+ type :string, check: proc { |pw| pw.include?('*') }
20
+ type :integer
21
+ end
22
+ end
23
+
24
+ schema.validate!(
25
+ naming: { first_name: 'John',
26
+ last_name: 'Doe' },
27
+ age: 34,
28
+ password: 'my*pass'
29
+ )
30
+ ```
31
+
32
+ ```ruby
33
+ schema2 = Schema.new do
34
+ req :description,
35
+ :string,
36
+ if: proc { |str| str.start_with?('Abstract: ') },
37
+ max: 35,
38
+ check: proc { |str| !str.end_with?('.') }
39
+ req :description, :string, min: 35
40
+ end
41
+
42
+ schema2.validate!(description: 'Abstract: a short description')
43
+ schema2.validate!(description: 'Since this is no abstract, we expect it to be longer.')
44
+ ```
45
+
46
+ ## Installation
47
+
48
+ To install the **Schemacop** gem:
49
+
50
+ ```sh
51
+ $ gem install schemacop
52
+ ```
53
+
54
+ To install it using `bundler` (recommended for any application), add it to your
55
+ `Gemfile`:
56
+
57
+ ```ruby
58
+ gem 'schemacop'
59
+ ```
60
+
61
+ ## Basics
62
+
63
+ Since there is no explicit typing in Ruby, it can be hard to make sure that a
64
+ method is recieving exactly the right kind of data it needs. The idea of this
65
+ gem is to define a schema at boot time that will validate the data being passed
66
+ around at runtime. Those two steps look as follows:
67
+
68
+ At boot time:
69
+
70
+ ```ruby
71
+ my_schema = Schema.new do
72
+ # Your specification goes here
73
+ end
74
+ ```
75
+
76
+ At runtime:
77
+
78
+ ```ruby
79
+ my_schema.validate!(
80
+ # Your data goes here
81
+ )
82
+ ```
83
+
84
+ `validate!` will fail if the data given to it does not match what was specified
85
+ in the schema.
86
+
87
+ ### Type lines vs. Field lines
88
+
89
+ Schemacop uses a DSL (domain-specific language) to let you describe your
90
+ schemas. We distinguish between two kinds of identifiers:
91
+
92
+ - Field Lines: We call a key-value pair (like the contents of a hash) a *field*.
93
+ A field line typically starts with the keyword `req` (for a required field) or
94
+ `opt` (for an optional field).
95
+
96
+ - Type Lines: Those start with the keyword `type` and specify the data type to
97
+ be accepted with a corresponding symbol (e.g. `:integer` or `:boolean`). You
98
+ can have multiple Type Lines for a Field Line in order to indicate that the
99
+ field's value can be of one of the specified types.
100
+
101
+ If you don't use any short forms, a schema definition would be something like
102
+ this:
103
+
104
+ ```ruby
105
+ s = Schema.new do
106
+ type :integer
107
+ type :hash do
108
+ req 'present' do
109
+ type :boolean
110
+ end
111
+ end
112
+ end
113
+ ```
114
+
115
+ The above schema would accept either an integer or a hash with exactly one field
116
+ with key 'present' of type String and value of type Boolean (either TrueClass or
117
+ FalseClass).
118
+
119
+ We will see Type and Field lines in more detail below.
120
+
121
+ ### `validate` vs `validate!` vs `valid?`
122
+
123
+ The method `validate` will return a `Collector` object that contains all
124
+ validation errors (if any) as well as a deep copy of your data with applied
125
+ defaults and castings, whereas `validate!` will accumulate all violations
126
+ and finally throw an exception describing them or, if the validation was
127
+ successful, a deep-copy of your supplied data with defaults and castings
128
+ applied.
129
+
130
+ For simply querying the validity of some data, use the methods `valid?` or
131
+ `invalid?`.
132
+
133
+ Examples:
134
+
135
+ ```ruby
136
+ # validate! returns your modified data or throws a validation error
137
+ s = Schema.new do
138
+ req :foo, default: 42
139
+ end
140
+ s.validate!({}) # => { foo: 42 }
141
+
142
+ # validate returns a collector
143
+ s = Schema.new do
144
+ req :foo, default: 42
145
+ end
146
+
147
+ collector = s.validate({})
148
+ collector.valid? # true
149
+ collector.data # => { foo: 42 }
150
+
151
+ collector = s.validate({ foo: 'invalid' })
152
+ collector.valid? # false
153
+ collector.data # => nil
154
+ collector.exceptions # => Validation error
155
+ ```
156
+
157
+
158
+ ## Schemacop's DSL
159
+
160
+ In this section, we will ignore [short forms](#short-forms) and explicitly
161
+ write out everything.
162
+
163
+ Inside the block given at the schema instantiation (`Schema.new do ... end`),
164
+ the following kinds of method calls are allowed (where the outermost must be a
165
+ Type Line):
166
+
167
+ ### Type Line
168
+
169
+ A Type Line always starts with the identifier `type` and specifies a possible
170
+ data type for a given field (if inside a Field Line) or the given data structure
171
+ (if directly below the schema instantiation).
172
+
173
+ Type Lines are generally of the form
174
+
175
+ ```ruby
176
+ type :my_type, option_1: value_1, ..., option_n: value_n
177
+ ```
178
+
179
+ where `:my_type` is a supported symbol (see section [Types](#types) below for
180
+ supported types).
181
+
182
+ #### General options
183
+
184
+ Some types support specific options that allow additional checks on the nature
185
+ of the data (such as the `min` option for type `:number`). The following options
186
+ are supported by all types:
187
+
188
+ ##### Option `if`
189
+
190
+ This option takes a proc (or a lambda) as value. The proc will be called when
191
+ checking whether or not the data being analyzed fits a certain type. The data is
192
+ given to the proc, which has to return either true or false. If it returns true,
193
+ the type of the given data is considered correct and the data will be validated
194
+ if further options are given.
195
+
196
+ Note that the proc in `if` will only get called if the type (`:my_type` from
197
+ above) fits the data already. You can use the option `if` in order to say: "Even
198
+ if the data is of type `:my_type`, I consider it having the wrong type if my
199
+ proc returns false."
200
+
201
+ Consider a scenario in which you want to have the following rule set:
202
+
203
+ - Only integers may be given
204
+ - Odd integers must be no larger than 15
205
+ - No limitations for even integers
206
+
207
+ The corresponding schema would look as follows:
208
+
209
+ ```ruby
210
+ Schema.new do
211
+ type :integer, if: proc { |data| data.odd? }, max: 15
212
+ type :integer
213
+ end
214
+ ```
215
+
216
+ Here, the first type line will only accept odd numbers and the option `max: 15`
217
+ provided by the `:integer` validator will discard numbers higher than 15.
218
+
219
+ Since the first line only accepts odd numbers, it doesn't apply for even numbers
220
+ (due to the proc given to `if` they are considered to be of the wrong type) and
221
+ control falls through to the second type line accepting any integer.
222
+
223
+ ##### Option `check`
224
+
225
+ This option allows you to perform arbitrary custom checks for a given data type.
226
+ Just like `if`, `check` takes a proc or lambda as a value, but it runs *after*
227
+ the type checking, meaning that it only gets executed if the data has the right
228
+ type and the proc in `if` (if any) has returned true.
229
+
230
+ The proc passed to the `check` option is given the data being analyzed. It is to
231
+ return true if the data passes the custom check. If it returns false or an error
232
+ message as a string, Schemacop considers the data to be invalid.
233
+
234
+ The following example illustrates the use of the option `check`: Consider a
235
+ scenario in which you want the following rule set:
236
+
237
+ - Data must be of type String
238
+ - The string must be longer than 5 characters
239
+ - The second character must be an 'r'
240
+
241
+ The corresponding schema would look as follows:
242
+
243
+ ```ruby
244
+ Schema.new do
245
+ type :string, min: 5, check: proc { |data| data[1] == 'r'}
246
+ end
247
+ ```
248
+
249
+ The above Type Line has type `:string` and two options (`min` and `check`). The
250
+ option `min` is supported by the `:string` validator (covered later).
251
+
252
+ You can also specify a custom error message by returning a string:
253
+
254
+
255
+ ```ruby
256
+ Schema.new do
257
+ type :integer, check: proc { |i| i.even? ? true : 'Custom error' }
258
+ end
259
+ ```
260
+
261
+ This will include `Custom error` in the validation error message.
262
+
263
+ ### Field Line
264
+
265
+ Inside a Type Line of type `:hash`, you may specify an arbitrary number of field
266
+ lines (one for each key-value pair you want to be in the hash).
267
+
268
+ Field Lines start with one of the following six identifiers: `req`, `req?`,
269
+ `req!`, `opt`, `opt?` or `opt!`:
270
+
271
+ - The suffix `-!` means that the field must not be nil.
272
+
273
+ - The suffix `-?` means that the field may be nil.
274
+
275
+ - The prefix `req-` denotes a required field (validation fails if the given data
276
+ hash doesn't define it). `req` is a shorthand notation for `req!` (meaning
277
+ that by default, a required field cannot be nil).
278
+
279
+ - The prefix `opt-` denotes an optional field. `opt` is a shorthand notation for
280
+ `opt?` (meaning that by default, an optional field may be nil).
281
+
282
+ To summarize:
283
+
284
+ - `req` or `req!`: required and non-nil
285
+ - `req?`: required but may be nil
286
+ - `opt` or `opt?`: optional and may be nil
287
+ - `opt!`: optional but non-nil
288
+
289
+ You then pass a block with a single or multiple Type Lines to the field.
290
+
291
+ Example: The following schema defines a hash that has a required non-nil field
292
+ of type String under the key `:name` (of type Symbol) and an optional but
293
+ non-nil field of type Integer or Date under the key `:age`.
294
+
295
+ ```ruby
296
+ Schema.new do
297
+ type :hash do
298
+ req :name do
299
+ type :string
300
+ end
301
+ opt! :age do
302
+ type :integer
303
+ type :object, classes: Date
304
+ end
305
+ end
306
+ end
307
+ ```
308
+
309
+ You might find the notation cumbersome, and you'd be right to say so. Luckily
310
+ there are plenty of short forms available which we will see below.
311
+
312
+ #### Handling hashes with indifferent access
313
+
314
+ Schemacop has special handling for objects of the class
315
+ `ActiveSupport::HashWithIndifferentAccess`: You may specify the keys as symbols
316
+ or strings, and Schemacop will handle the conversion necessary for proper
317
+ validation internally. Note that if you define the same key as string and
318
+ symbol, it will throw a `ValidationError` [exception](#exceptions) when asked to
319
+ validate a hash with indifferent access.
320
+
321
+ Thus, the following two schema definitions are equivalent when validating a hash
322
+ with indifferent access:
323
+
324
+ ```ruby
325
+ Schema.new do
326
+ type :hash do
327
+ req :name do
328
+ type :string
329
+ end
330
+ end
331
+ end
332
+
333
+ Schema.new do
334
+ type :hash do
335
+ req 'name' do
336
+ type :string
337
+ end
338
+ end
339
+ end
340
+ ```
341
+
342
+ ## Types
343
+
344
+ Types are defined via their validators, which is a class under `validator/`.
345
+ Each validator is sourced by `schemacop.rb`.
346
+
347
+ The following types are supported by Schemacop by default:
348
+
349
+ * `:boolean` accepts a Ruby TrueClass or FalseClass instance.
350
+
351
+ * `:integer` accepts a Ruby Integer.
352
+
353
+ - supported options: `min`, `max` (lower / upper bound)
354
+
355
+ * `:float` accepts a Ruby Float.
356
+
357
+ - supported options: `min`, `max` (lower / upper bound)
358
+
359
+ * `:number` accepts a Ruby Integer or Float.
360
+
361
+ - supported options: `min`, `max` (lower / upper bound)
362
+
363
+ * `:string` accepts a Ruby String.
364
+
365
+ - supported options: `min`, `max` (bounds for string length)
366
+
367
+ * `:symbol` accepts a Ruby Symbol.
368
+
369
+ * `:object` accepts an arbitrary Ruby object (any object if no option is given).
370
+
371
+ Supported options:
372
+
373
+ - `classes`: Ruby class (or an array of them) that will be the only recognized
374
+ filters. Unlike other options, this one affects not the validation but the
375
+ type recognition, meaning that you can have multiple Type Lines with
376
+ different `classes` option for the same field, each having its own
377
+ validation (e.g. through the option `check`).
378
+
379
+ - `strict`: Boolean option, defaults to true. If set to false, the validator
380
+ also allows derived classes of those specified with `classes`.
381
+
382
+ * `:array` accepts a Ruby Array.
383
+
384
+ - supported options: `min`, `max` (bounds for array size) and `nil`: TODO
385
+
386
+ - accepts a block with an arbitrary number of Type Lines.
387
+
388
+ - TODO no lookahead for different arrays, see
389
+ validator_array_test#test_multiple_arrays
390
+
391
+ * `:hash` accepts a Ruby Hash or an `ActiveSupport::HashWithIndifferentAccess`.
392
+
393
+ - accepts a block with an arbitrary number of Field Lines.
394
+
395
+ - `allow_obsolete_keys`: If enabled (default `false`), this allows arbitrary
396
+ keys within your hash. Keys not specified in your schema will not be
397
+ validated further, but keys specified explicitly are still validated.
398
+
399
+ * `:nil`: accepts a Ruby NilClass instance. If you want to allow `nil` as a
400
+ value in a field, see above for the usage of the suffixes `-!` and `-?` for
401
+ Field Lines.
402
+
403
+ All types support the options `if` and `check` (see the section about Type Lines
404
+ above).
405
+
406
+ ## Short forms
407
+
408
+ For convenience, the following short forms may be used (and combined if
409
+ possible).
410
+
411
+ ### Passing a type to a Field Line or schema
412
+
413
+ Instead of adding a Type Line in the block of a Field Line, you can omit `do
414
+ type ... end` and directly write the type after the key of the field.
415
+
416
+ Note that when using this short form, you may not give a block to the Field
417
+ Line.
418
+
419
+ ```ruby
420
+ # Long form
421
+ req :name do
422
+ type :string, min: 2, max: 5
423
+ end
424
+
425
+ # Short form
426
+ req :name, :string, min: 2, max: 5
427
+ ```
428
+
429
+ This means that the value under the key `:name` of type Symbol must be a String
430
+ containing 2 to 5 characters.
431
+
432
+ The short form also works in the schema instantiation:
433
+
434
+ ```ruby
435
+ # Long form
436
+ Schema.new do
437
+ type :string, min: 2, max: 5
438
+ end
439
+
440
+ # Short form
441
+ Schema.new(:string, min: 2, max: 5)
442
+ ```
443
+
444
+ This means that the data given to the schema must be a String that is between 2
445
+ and 5 characters long.
446
+
447
+ ### Passing multiple types at once
448
+
449
+ You can specify several types at once by putting them in an array.
450
+
451
+ Note that when using this short form, you may not give any options.
452
+
453
+ ```ruby
454
+ # Long form
455
+ opt! :age do
456
+ type :string
457
+ type :integer
458
+ type :boolean
459
+ end
460
+
461
+ # Short form
462
+ opt! :age do
463
+ type [:string, :integer, :boolean]
464
+ end
465
+ ```
466
+
467
+ Combined with previous short form:
468
+
469
+ ```ruby
470
+ opt! :age, [:string, :integer, :boolean]
471
+ ```
472
+
473
+ This also works in the schema instantiation:
474
+
475
+ ```ruby
476
+ Schema.new([:string, :integer, :boolean])
477
+ ```
478
+
479
+ This means that the schema will validate any data of type String, Integer,
480
+ TrueClass or FalseClass.
481
+
482
+ ### Omitting the Type Line in a Field Line
483
+
484
+ If you don't specify the type of a field, it will default to `:object` with no
485
+ options, meaning that the field will accept any kind of data:
486
+
487
+ ```ruby
488
+ # Long form
489
+ req? :child do
490
+ type :object
491
+ end
492
+
493
+ # Short form
494
+ req? :child
495
+ ```
496
+
497
+ ### Omitting the Type Line in schema instantiation
498
+
499
+ If you don't give a Type Line to a schema, it will accept data of type Hash.
500
+ Therefore, if you validate Hashes only, you can omit the Type Line and directly
501
+ write Field Lines in the schema instantiation:
502
+
503
+ ```ruby
504
+ # Long form
505
+ Schema.new do
506
+ type :hash do
507
+ req :name do
508
+ # ...
509
+ end
510
+ end
511
+ end
512
+
513
+ # Short form
514
+ Schema.new do
515
+ req :name do
516
+ # ...
517
+ end
518
+ end
519
+ ```
520
+
521
+ Note that this does not allow you to specify any options for the hash itself.
522
+ You still need to specify `:hash` as a type if you want to pass any options to
523
+ the hash (i.e. a `default`).
524
+
525
+ ### Shortform for subtypes
526
+
527
+ In case of nested arrays, you can group all Type Lines to a single one.
528
+
529
+ Note that any options or block passed to the grouped Type Line will be given to
530
+ the innermost (last) type.
531
+
532
+ ```ruby
533
+ # Long form
534
+ type :array do
535
+ type :integer, min: 3
536
+ end
537
+
538
+ # Short form
539
+ type :array, :integer, min: 3
540
+ ```
541
+
542
+ A more complex example:
543
+
544
+ Long form:
545
+
546
+ ```ruby
547
+ Schema.new do
548
+ type :hash do
549
+ req 'nutrition' do
550
+ type :array do
551
+ type :array do
552
+ type :hash, check: proc { |h| h.member?(:food) || h.member?(:drink) } do
553
+ opt! :food do
554
+ type :object
555
+ end
556
+ opt! :drink do
557
+ type :object
558
+ end
559
+ end
560
+ end
561
+ end
562
+ end
563
+ end
564
+ end
565
+ ```
566
+
567
+ Short form (with this short form others from above):
568
+
569
+ ```ruby
570
+ Schema.new do
571
+ req 'nutrition', :array, :array, :hash, check: proc { |h| h.member?(:food) || h.member?(:drink) } do
572
+ opt! :food
573
+ opt! :drink
574
+ end
575
+ end
576
+ ```
577
+
578
+ This example accepts a hash with exactly one String key 'nutrition' with value
579
+ of type Array with children of type Array with children of type Hash in which at
580
+ least one of the Symbol keys `:food` and `:drink` (with any non-nil value type)
581
+ is present.
582
+
583
+ ## Defaults
584
+
585
+ Starting from version 2.4.0, Schemacop allows you to define default values at
586
+ any point in your schema. If the validated data contains a nil value, it will be
587
+ substituted by the given default value.
588
+
589
+ Note that Schemacop never modifies the data you pass to it. If you want to
590
+ benefit from Schemacop-applied defaults, you need to access the cloned, modified
591
+ data returned by `validate` or `validate!`.
592
+
593
+ Applying defaults is done before validating the substructure and before any type
594
+ casting. The provided default will be validated same as user-supplied data, so
595
+ if your given default does not validate properly, a validation error is thrown.
596
+ Make sure your default values always match the underlying schema.
597
+
598
+ Defaults can be specified at any point:
599
+
600
+
601
+ ```ruby
602
+ # Basic usage
603
+ Schema.new do
604
+ type :string, default: 'Hello World'
605
+ end
606
+
607
+ # The default given for the first type will match
608
+ Schema.new do
609
+ type :string, default: 'Hello World' # This will always be applied of no value is supplied
610
+ type :integer, default: 42
611
+ end
612
+
613
+ # You can also pass entire hashes or arrays to your defaults
614
+ Schema.new do
615
+ req :foo, :hash, default: { foo: :bar } do
616
+ req :foo, :symbol
617
+ end
618
+ req :bar, :array, :integer, default: [1, 2, 3]
619
+ end
620
+
621
+ # Defaults must match the given schema. The following will fail.
622
+ Schema.new do
623
+ req :foo, default: { bar: :baz } do
624
+ req :foo
625
+ end
626
+ end
627
+
628
+ # You can also specify blocks (without params) as defaults that will be
629
+ # evaluated at time of validation.
630
+ Schema.new do
631
+ opt :year, :integer, default: ->() { Time.now.year }
632
+ end
633
+
634
+ # If, for some very specific reason, you *need* the default to be an actual
635
+ # proc, wrap it inside another proc.
636
+ Schema.new do
637
+ opt :myproc, Proc, default: ->() { ->() { 42 } }
638
+ end
639
+ ```
640
+
641
+ ### Required data points
642
+
643
+ Note that any *required* validation is done before applying the defaults. If you
644
+ specify a `req` field, it must always be given, no matter if you have specified
645
+ a default or not. Therefore, specifying `req` fields do not make sense in
646
+ conjunction with defaults, as the default is always ignored.
647
+
648
+ ## Type casting
649
+
650
+ Starting from version 2.4.0, Schemacop allows you to specify type castings that
651
+ can alter the validated data. Consider the following:
652
+
653
+ ```ruby
654
+ s = Schema.new do
655
+ req :id, :integer, cast: [String]
656
+ end
657
+
658
+ data = s.validate!(id: '42')
659
+ data # => { id: 42 }
660
+ ```
661
+
662
+ Note that Schemacop never modifies the data you pass to it. If you want to
663
+ benefit from Schemacop-applied castings, you need to access the cloned, modified
664
+ data returned by `validate` or `validate!`.
665
+
666
+ ### Specifying type castings
667
+
668
+ Type castings can be specified using two forms: Either as a hash or as an array.
669
+ While using an array only allows you to specify the supported source types to be
670
+ casted, using a hash allows you to specify custom casting logic as blocks.
671
+
672
+ For hashes, the key must be a class and the value must be either `:default` for
673
+ using a built-in caster or a callable object (proc or lambda) that receives the
674
+ value and is supposed to cast it. If the value can't be casted, the proc must
675
+ fail with an exception. The exception message will then be contained in the
676
+ collected validation errors.
677
+
678
+ Example:
679
+
680
+ ```ruby
681
+ Schema.new do
682
+ # Pass array to `cast`. This enables casting from String or Float to Integer
683
+ # using the built-in casters.
684
+ req :id_1, :integer, cast: [String, Float]
685
+
686
+ # Pass hash to `cast`. This enables casting from Float to Integer using the
687
+ # built-in caster and from String to Integer using a custom callback.
688
+ req :id_2, :integer, cast: { Float => :default, String => proc { |s| Integer(s) }
689
+ end
690
+ ```
691
+
692
+ ### Built-in casters
693
+
694
+ Schemacop comes with the following casters:
695
+
696
+ - `String` to `Integer` and `Float`
697
+ - `Float` to `Integer`
698
+ - `Integer` to `Float`
699
+
700
+ Note that all built-in casters are precise, so the string `foo` will fail with
701
+ an error if casted to an Integer. When casting float values and strings
702
+ containing float values to integers, the decimal places will be discarded
703
+ however.
704
+
705
+ ### Execution order
706
+
707
+ The casting is done *before* the options `if` and `check` are evaluated.
708
+ Example:
709
+
710
+ ```ruby
711
+ s = Schema.new do
712
+ type :integer, if: proc { |i| i == 42 } # 1
713
+ type :integer, check: proc { |i| i < 3 } # 2
714
+ type :string
715
+ end
716
+
717
+ s.validate!('42') # 1 will match
718
+ s.validate!('2') # 2 will match
719
+ s.validate!('234') # 3 will match
720
+ s.validate!(5) # Will fail, as nothing matches
721
+ ```
722
+
723
+ ### Caveats
724
+
725
+ Casting only works with type definitions that only include one type. For
726
+ instance, the `Numeric` validator includes both `Integer` and `Float`, which
727
+ would made it unclear what to cast a string into:
728
+
729
+ ```ruby
730
+ # This does not work, as it is unclear whether to cast the String into an
731
+ # Integer or a Float.
732
+ type :number, cast: [String]
733
+ ```
734
+
735
+ The same also applies to booleans, as they compound both `TrueClass` and
736
+ `FalseClass`. This may be tackled in future releases.
737
+
738
+ ## Exceptions
739
+
740
+ Schemacop will throw one of the following checked exceptions:
741
+
742
+ * {Schemacop::Exceptions::InvalidSchemaError}
743
+
744
+ This exception is thrown when the given schema definition format is invalid.
745
+
746
+ * {Schemacop::Exceptions::ValidationError}
747
+
748
+ This exception is thrown when the given data does not comply with the given
749
+ schema definition.
750
+
751
+ ## Known limitations
752
+
753
+ * Schemacop does not yet allow cyclic structures with infinite depth.
754
+
755
+ * Schemacop is not made for validating complex causalities (i.e. field `a`
756
+ needs to be given only if field `b` is present).
757
+
758
+ * Schemacop does not yet support string regex matching.
759
+
760
+ ## Development
761
+
762
+ To run tests:
763
+
764
+ * Check out the source
765
+
766
+ * Run `bundle install`
767
+
768
+ * Run `bundle exec rake test` to run all tests
769
+
770
+ * Run `bundle exec rake test TEST=test/unit/some/file.rb` to run a single test
771
+ file
772
+
773
+ ## Copyright
774
+
775
+ Copyright (c) 2020 Sitrox. See `LICENSE` for further details.