schemacop 2.4.6 → 3.0.0.rc3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (174) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +3 -0
  3. data/.releaser_config +0 -1
  4. data/.rubocop.yml +25 -1
  5. data/.travis.yml +3 -1
  6. data/CHANGELOG.md +33 -0
  7. data/README.md +53 -710
  8. data/README_V2.md +775 -0
  9. data/README_V3.md +1197 -0
  10. data/Rakefile +8 -12
  11. data/VERSION +1 -1
  12. data/lib/schemacop.rb +35 -36
  13. data/lib/schemacop/base_schema.rb +37 -0
  14. data/lib/schemacop/railtie.rb +10 -0
  15. data/lib/schemacop/schema.rb +1 -60
  16. data/lib/schemacop/schema2.rb +22 -0
  17. data/lib/schemacop/schema3.rb +21 -0
  18. data/lib/schemacop/scoped_env.rb +25 -13
  19. data/lib/schemacop/v2.rb +25 -0
  20. data/lib/schemacop/{caster.rb → v2/caster.rb} +16 -2
  21. data/lib/schemacop/{collector.rb → v2/collector.rb} +5 -2
  22. data/lib/schemacop/{dupper.rb → v2/dupper.rb} +1 -1
  23. data/lib/schemacop/{field_node.rb → v2/field_node.rb} +4 -3
  24. data/lib/schemacop/v2/node.rb +142 -0
  25. data/lib/schemacop/{node_resolver.rb → v2/node_resolver.rb} +1 -1
  26. data/lib/schemacop/v2/node_supporting_field.rb +70 -0
  27. data/lib/schemacop/{node_supporting_type.rb → v2/node_supporting_type.rb} +8 -5
  28. data/lib/schemacop/{node_with_block.rb → v2/node_with_block.rb} +3 -2
  29. data/lib/schemacop/v2/root_node.rb +0 -0
  30. data/lib/schemacop/v2/validator/array_validator.rb +32 -0
  31. data/lib/schemacop/{validator → v2/validator}/boolean_validator.rb +1 -1
  32. data/lib/schemacop/v2/validator/float_validator.rb +7 -0
  33. data/lib/schemacop/v2/validator/hash_validator.rb +37 -0
  34. data/lib/schemacop/v2/validator/integer_validator.rb +7 -0
  35. data/lib/schemacop/{validator → v2/validator}/nil_validator.rb +1 -1
  36. data/lib/schemacop/v2/validator/number_validator.rb +21 -0
  37. data/lib/schemacop/v2/validator/object_validator.rb +29 -0
  38. data/lib/schemacop/v2/validator/string_validator.rb +39 -0
  39. data/lib/schemacop/{validator → v2/validator}/symbol_validator.rb +1 -1
  40. data/lib/schemacop/v3.rb +45 -0
  41. data/lib/schemacop/v3/all_of_node.rb +27 -0
  42. data/lib/schemacop/v3/any_of_node.rb +28 -0
  43. data/lib/schemacop/v3/array_node.rb +218 -0
  44. data/lib/schemacop/v3/boolean_node.rb +16 -0
  45. data/lib/schemacop/v3/combination_node.rb +45 -0
  46. data/lib/schemacop/v3/context.rb +17 -0
  47. data/lib/schemacop/v3/dsl_scope.rb +46 -0
  48. data/lib/schemacop/v3/global_context.rb +114 -0
  49. data/lib/schemacop/v3/hash_node.rb +258 -0
  50. data/lib/schemacop/v3/integer_node.rb +13 -0
  51. data/lib/schemacop/v3/is_not_node.rb +32 -0
  52. data/lib/schemacop/v3/node.rb +219 -0
  53. data/lib/schemacop/v3/node_registry.rb +49 -0
  54. data/lib/schemacop/v3/number_node.rb +18 -0
  55. data/lib/schemacop/v3/numeric_node.rb +76 -0
  56. data/lib/schemacop/v3/object_node.rb +40 -0
  57. data/lib/schemacop/v3/one_of_node.rb +28 -0
  58. data/lib/schemacop/v3/reference_node.rb +49 -0
  59. data/lib/schemacop/v3/result.rb +58 -0
  60. data/lib/schemacop/v3/string_node.rb +124 -0
  61. data/lib/schemacop/v3/symbol_node.rb +13 -0
  62. data/schemacop.gemspec +24 -27
  63. data/test/lib/test_helper.rb +152 -0
  64. data/test/schemas/nested/group.rb +6 -0
  65. data/test/schemas/user.rb +7 -0
  66. data/test/unit/schemacop/v2/casting_test.rb +120 -0
  67. data/test/unit/schemacop/v2/collector_test.rb +47 -0
  68. data/test/unit/schemacop/v2/custom_check_test.rb +95 -0
  69. data/test/unit/schemacop/v2/custom_if_test.rb +97 -0
  70. data/test/unit/schemacop/v2/defaults_test.rb +95 -0
  71. data/test/unit/schemacop/v2/empty_test.rb +16 -0
  72. data/test/unit/schemacop/v2/nil_dis_allow_test.rb +43 -0
  73. data/test/unit/schemacop/v2/node_resolver_test.rb +28 -0
  74. data/test/unit/schemacop/v2/short_forms_test.rb +351 -0
  75. data/test/unit/schemacop/v2/types_test.rb +88 -0
  76. data/test/unit/schemacop/v2/validator_array_test.rb +99 -0
  77. data/test/unit/schemacop/v2/validator_boolean_test.rb +17 -0
  78. data/test/unit/schemacop/v2/validator_float_test.rb +59 -0
  79. data/test/unit/schemacop/v2/validator_hash_test.rb +95 -0
  80. data/test/unit/schemacop/v2/validator_integer_test.rb +48 -0
  81. data/test/unit/schemacop/v2/validator_nil_test.rb +15 -0
  82. data/test/unit/schemacop/v2/validator_number_test.rb +62 -0
  83. data/test/unit/schemacop/v2/validator_object_test.rb +141 -0
  84. data/test/unit/schemacop/v2/validator_string_test.rb +78 -0
  85. data/test/unit/schemacop/v2/validator_symbol_test.rb +18 -0
  86. data/test/unit/schemacop/v3/all_of_node_test.rb +198 -0
  87. data/test/unit/schemacop/v3/any_of_node_test.rb +218 -0
  88. data/test/unit/schemacop/v3/array_node_test.rb +815 -0
  89. data/test/unit/schemacop/v3/boolean_node_test.rb +126 -0
  90. data/test/unit/schemacop/v3/global_context_test.rb +164 -0
  91. data/test/unit/schemacop/v3/hash_node_test.rb +884 -0
  92. data/test/unit/schemacop/v3/integer_node_test.rb +323 -0
  93. data/test/unit/schemacop/v3/is_not_node_test.rb +173 -0
  94. data/test/unit/schemacop/v3/node_test.rb +148 -0
  95. data/test/unit/schemacop/v3/number_node_test.rb +292 -0
  96. data/test/unit/schemacop/v3/object_node_test.rb +170 -0
  97. data/test/unit/schemacop/v3/one_of_node_test.rb +187 -0
  98. data/test/unit/schemacop/v3/reference_node_test.rb +351 -0
  99. data/test/unit/schemacop/v3/string_node_test.rb +334 -0
  100. data/test/unit/schemacop/v3/symbol_node_test.rb +75 -0
  101. metadata +157 -150
  102. data/doc/Schemacop.html +0 -146
  103. data/doc/Schemacop/ArrayValidator.html +0 -329
  104. data/doc/Schemacop/BooleanValidator.html +0 -145
  105. data/doc/Schemacop/Caster.html +0 -379
  106. data/doc/Schemacop/Collector.html +0 -787
  107. data/doc/Schemacop/Dupper.html +0 -214
  108. data/doc/Schemacop/Exceptions.html +0 -115
  109. data/doc/Schemacop/Exceptions/InvalidSchemaError.html +0 -124
  110. data/doc/Schemacop/Exceptions/ValidationError.html +0 -124
  111. data/doc/Schemacop/FieldNode.html +0 -421
  112. data/doc/Schemacop/FloatValidator.html +0 -158
  113. data/doc/Schemacop/HashValidator.html +0 -293
  114. data/doc/Schemacop/IntegerValidator.html +0 -158
  115. data/doc/Schemacop/NilValidator.html +0 -145
  116. data/doc/Schemacop/Node.html +0 -1438
  117. data/doc/Schemacop/NodeResolver.html +0 -258
  118. data/doc/Schemacop/NodeSupportingField.html +0 -590
  119. data/doc/Schemacop/NodeSupportingType.html +0 -612
  120. data/doc/Schemacop/NodeWithBlock.html +0 -289
  121. data/doc/Schemacop/NumberValidator.html +0 -232
  122. data/doc/Schemacop/ObjectValidator.html +0 -298
  123. data/doc/Schemacop/RootNode.html +0 -171
  124. data/doc/Schemacop/Schema.html +0 -699
  125. data/doc/Schemacop/StringValidator.html +0 -295
  126. data/doc/Schemacop/SymbolValidator.html +0 -145
  127. data/doc/ScopedEnv.html +0 -351
  128. data/doc/_index.html +0 -379
  129. data/doc/class_list.html +0 -51
  130. data/doc/css/common.css +0 -1
  131. data/doc/css/full_list.css +0 -58
  132. data/doc/css/style.css +0 -496
  133. data/doc/file.README.html +0 -833
  134. data/doc/file_list.html +0 -56
  135. data/doc/frames.html +0 -17
  136. data/doc/index.html +0 -833
  137. data/doc/inheritance.graphml +0 -524
  138. data/doc/inheritance.pdf +0 -825
  139. data/doc/js/app.js +0 -303
  140. data/doc/js/full_list.js +0 -216
  141. data/doc/js/jquery.js +0 -4
  142. data/doc/method_list.html +0 -587
  143. data/doc/top-level-namespace.html +0 -112
  144. data/lib/schemacop/node.rb +0 -139
  145. data/lib/schemacop/node_supporting_field.rb +0 -58
  146. data/lib/schemacop/root_node.rb +0 -4
  147. data/lib/schemacop/validator/array_validator.rb +0 -30
  148. data/lib/schemacop/validator/float_validator.rb +0 -5
  149. data/lib/schemacop/validator/hash_validator.rb +0 -35
  150. data/lib/schemacop/validator/integer_validator.rb +0 -5
  151. data/lib/schemacop/validator/number_validator.rb +0 -19
  152. data/lib/schemacop/validator/object_validator.rb +0 -27
  153. data/lib/schemacop/validator/string_validator.rb +0 -37
  154. data/test/casting_test.rb +0 -100
  155. data/test/collector_test.rb +0 -45
  156. data/test/custom_check_test.rb +0 -93
  157. data/test/custom_if_test.rb +0 -95
  158. data/test/defaults_test.rb +0 -93
  159. data/test/empty_test.rb +0 -14
  160. data/test/nil_dis_allow_test.rb +0 -41
  161. data/test/node_resolver_test.rb +0 -26
  162. data/test/short_forms_test.rb +0 -349
  163. data/test/test_helper.rb +0 -13
  164. data/test/types_test.rb +0 -84
  165. data/test/validator_array_test.rb +0 -97
  166. data/test/validator_boolean_test.rb +0 -15
  167. data/test/validator_float_test.rb +0 -57
  168. data/test/validator_hash_test.rb +0 -93
  169. data/test/validator_integer_test.rb +0 -46
  170. data/test/validator_nil_test.rb +0 -13
  171. data/test/validator_number_test.rb +0 -60
  172. data/test/validator_object_test.rb +0 -139
  173. data/test/validator_string_test.rb +0 -76
  174. 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.