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,1197 @@
1
+ # Schemacop schema V3
2
+
3
+ ## Table of Contents
4
+
5
+ 1. [Validation](#validation)
6
+ 2. [Exceptions](#exceptions)
7
+ 3. [Generic Keywords](#generic-keywords)
8
+ 4. [Nodes](#nodes)
9
+ 1. [String](#string)
10
+ 2. [Integer](#integer)
11
+ 3. [Number](#number)
12
+ 4. [Symbol](#symbol)
13
+ 5. [Boolean](#boolean)
14
+ 6. [Array](#array)
15
+ 7. [Hash](#hash)
16
+ 8. [Object](#object)
17
+ 9. [AllOf](#allOf)
18
+ 10. [AnyOf](#anyOf)
19
+ 11. [OneOf](#oneOf)
20
+ 12. [IsNot](#isNot)
21
+ 13. [Reference](#reference)
22
+ 5. [Context](#context)
23
+ 6. [External schemas](#external-schemas)
24
+
25
+ ## Validation
26
+
27
+ Using Schemacop, you can either choose to validate your data either using the
28
+ graceful `validate` method, or the bang variant, `validate!`.
29
+
30
+ The `validate` method on a schema with some supplied data will return a
31
+ `Schemacop::Result` object, which has some useful methods to work with the
32
+ data you validated.
33
+
34
+ ```ruby
35
+ schema = Schemacop::Schema3.new :string, format: :date
36
+ result = schema.validate('2020-01-01')
37
+ result.class # => Schemacop::Result
38
+ ```
39
+
40
+ With the `data` method, you can access the casted version of your data:
41
+
42
+ ```ruby
43
+ schema = Schemacop::Schema3.new :string, format: :date
44
+ result = schema.validate('2020-01-01')
45
+ result.data # => Wed, 01 Jan 2020
46
+ ```
47
+
48
+ And with the `valid?` method, you can check if the supplied data validates
49
+ against the schema:
50
+
51
+ ```ruby
52
+ schema = Schemacop::Schema3.new :string, format: :date
53
+ result = schema.validate('2020-01-01')
54
+ result.valid? # => true
55
+ ```
56
+
57
+ On the other hand, the `validate!` method either returns the casted data if the
58
+ validation was successful, or if the validation failed, raises a
59
+ `Schemacop::Exceptions::ValidationError` exception:
60
+
61
+ ```ruby
62
+ schema = Schemacop::Schema3.new :string, format: :date
63
+ schema.validate!('2020-01-01') # => Wed, 01 Jan 2020
64
+ schema.validate!('Foo') # => Schemacop::Exceptions::ValidationError: /: String does not match format "date".
65
+ ```
66
+
67
+ ## Exceptions
68
+
69
+ Schemacop can raise the following exceptions:
70
+
71
+ * `Schemacop::Exceptions::ValidationError`: This exception is raised when the
72
+ `validate!` method is used, and the data that was passed in is invalid. The
73
+ exception message contains additional information why the validation failed.
74
+
75
+ Example:
76
+
77
+ ```ruby
78
+ schema = Schemacop::Schema3.new do
79
+ int! :foo
80
+ end
81
+
82
+ schema.validate!(foo: 'bar')
83
+ # => Schemacop::Exceptions::ValidationError: /foo: Invalid type, expected "integer".
84
+ ```
85
+
86
+ * `Schemacop::Exceptions::InvalidSchemaError`: This exception is raised when the
87
+ schema itself is not valid. The exception message contains additional
88
+ information why the validation failed.
89
+
90
+ Example:
91
+
92
+ ```ruby
93
+ Schemacop::Schema3.new do
94
+ int!
95
+ end
96
+
97
+ # => Schemacop::Exceptions::InvalidSchemaError: Child nodes must have a name.
98
+ ```
99
+
100
+ ## Generic Keywords
101
+
102
+ The nodes in Schemacop v3 also support generic keywords, similar to JSON schema:
103
+
104
+ * `title`: Short string, should be self-explanatory
105
+ * `description`: Description of the schema
106
+ * `examples`: Here, you can provide examples which will be valid for the schema
107
+ * `enum`: Here, you may enumerate values which will be valid, if the provided
108
+ value is not in the array, the validation will fail
109
+ * `default`: You may provide a default value for items that will be set if the
110
+ value is not given
111
+
112
+ The three keywords `title`, `description` and `examples` aren't used for validation,
113
+ but can be used to document the schema. They will be included in the JSON output
114
+ when you use the `as_json` method:
115
+
116
+ ```ruby
117
+ schema = Schemacop::Schema3.new :hash do
118
+ str! :name, title: 'Name', description: 'Holds the name of the user', examples: ['Joe', 'Anna']
119
+ end
120
+
121
+ schema.as_json
122
+
123
+ # => {"properties"=>{"name"=>{"type"=>"string", "title"=>"Name", "examples"=>["Joe", "Anna"], "description"=>"Holds the name of the user"}}, "additionalProperties"=>false, "required"=>["name"], "type"=>"object"}
124
+ ```
125
+
126
+ The `enum` keyword can be used to only allow a subset of values:
127
+
128
+ ```ruby
129
+ schema = Schemacop::Schema3.new :string, enum: ['foo', 'bar']
130
+
131
+ schema.validate!('foo') # => "foo"
132
+ schema.validate!('bar') # => "bar"
133
+ schema.validate!('baz') # => Schemacop::Exceptions::ValidationError: /: Value not included in enum ["foo", "bar"].
134
+ ```
135
+
136
+ Please note that you can also specify values in the enum that are not valid for
137
+ the schema. This means that the validation will still fail:
138
+
139
+ ```ruby
140
+ schema = Schemacop::Schema3.new :string, enum: ['foo', 'bar', 42]
141
+
142
+ schema.validate!('foo') # => "foo"
143
+ schema.validate!('bar') # => "bar"
144
+ schema.validate!(42) # => Schemacop::Exceptions::ValidationError: /: Invalid type, expected "string".
145
+ ```
146
+
147
+ The enum will also be provided in the json output:
148
+
149
+ ```ruby
150
+ schema = Schemacop::Schema3.new :string, enum: ['foo', 'bar']
151
+
152
+ schema.as_json
153
+ # => {"type"=>"string", "enum"=>["foo", "bar", 42]}
154
+ ```
155
+
156
+ And finally, the `default` keyword lets you set a default value to use when no
157
+ value is provided:
158
+
159
+ ```ruby
160
+ schema = Schemacop::Schema3.new :string, default: 'Schemacop'
161
+
162
+ schema.validate!('foo') # => "foo"
163
+ schema.validate!(nil) # => "Schemacop"
164
+ ```
165
+
166
+ The default value will also be provided in the json output:
167
+
168
+ ```ruby
169
+ schema = Schemacop::Schema3.new :string, default: 'Schemacop'
170
+
171
+ schema.as_json
172
+ # => {"type"=>"string", "default"=>"Schemacop"}
173
+ ```
174
+
175
+ Note that the default value you use is also validated against the schema:
176
+
177
+ ```ruby
178
+ schema = Schemacop::Schema3.new :string, default: 42
179
+
180
+ schema.validate!('foo') # => "foo"
181
+ schema.validate!(nil) # => Schemacop::Exceptions::ValidationError: /: Invalid type, expected "string".
182
+ ```
183
+
184
+ ## Nodes
185
+
186
+ ### String
187
+
188
+ Type: `:string`\
189
+ DSL: `str`
190
+
191
+ The string type is used for strings of text and must be a ruby `String` object
192
+ or a subclass. Using the option `format`, strings can be validated against and
193
+ transformed into various types.
194
+
195
+ #### Options
196
+
197
+ * `min_length`
198
+ Defines the (inclusive) minimum required string length
199
+ * `max_length`
200
+ Defines the (inclusive) maximum required string length
201
+ * `pattern`
202
+ Defines a (ruby) regex pattern the value will be matched against. Must be a
203
+ string and should generally start with `^` and end with `$` so as to evaluate
204
+ the entire string. It should not be enclosed in `/` characters.
205
+ * `format`
206
+ The `format` option allows for basic semantic validation on certain kinds of
207
+ string values that are commonly used. See section *formats* for more
208
+ information on the available formats. Note that strings with a format are also
209
+ **casted** into that format.
210
+
211
+ #### Formats
212
+
213
+ * `date`
214
+ A date according to [ RFC 3339, section
215
+ 5.6.](https://json-schema.org/latest/json-schema-validation.html#RFC3339) date
216
+ format, i.e. `2018-11-13`. Strings with this format will be
217
+ casted to a ruby `Date` object.
218
+
219
+ * `date_time`
220
+ A date time according to [RFC 3339, section
221
+ 5.6.](https://json-schema.org/latest/json-schema-validation.html#RFC3339) date
222
+ format, i.e. `2018-11-13T20:20:39+00:00`. Strings with this format will be
223
+ casted to a ruby `DateTime` object. The time zones will be inferred by the
224
+ string.
225
+
226
+ * `email`
227
+ Validates for a valid email address. There is no casting involved since email
228
+ addresses do not have their own ruby type.
229
+
230
+ * `boolean`
231
+ The string must be either `true` or `false`. This value will be casted to
232
+ Ruby's `TrueClass` or `FalseClass`.
233
+
234
+ * `binary`
235
+ The string is expected to contain binary contents. No casting or additional
236
+ validation is performed.
237
+
238
+ * `integer`
239
+ The string must be an integer and will be casted to a ruby `Integer` object.
240
+
241
+ * `number`
242
+ The string must be a number and will be casted to a ruby `Float` object.
243
+
244
+ #### Examples
245
+
246
+ ```ruby
247
+ # By using a format, string values are casted to that respective format
248
+ schema = Schemacop::Schema3.new(:string, format: :date)
249
+ result = schema.validate('1980-01-13')
250
+ result.data # => Date<"Sun, 13 Jan 1980">
251
+ ```
252
+
253
+ ### Integer
254
+
255
+ Type: `:integer`\
256
+ DSL: `int`
257
+
258
+ The integer type is used for whole numbers and must be a ruby `Integer` or a
259
+ subclass. With the various available options, validations on the value of the
260
+ integer can be done.
261
+
262
+ #### Options
263
+
264
+ * `minimum`
265
+ Defines an (inclusive) minimum, i.e. the number has to be equal or larger than the
266
+ given number
267
+ * `exclusive_minimum`
268
+ Defines an exclusive minimum, i.e. the number has to larger than the given number
269
+ * `maximum`
270
+ Defines an (inclusive) maximum, i.e. the number has to be equal or smaller than the
271
+ given number
272
+ * `exclusive_maximum`
273
+ Defines an exclusive maximum, i.e. the number has to smaller than the given number
274
+ * `multiple_of`
275
+ The received number has to be a multiple of the given number for the validation to
276
+ pass.
277
+
278
+ #### Examples
279
+
280
+ ```ruby
281
+ # Validates that the input is an even number between 0 and 100 (inclusive)
282
+ schema = Schemacop::Schema3.new(:integer, minimum: 0, maximum: 100, multiple_of: 2)
283
+ schema.validate!(42) # => 42
284
+ schema.validate!(43) # => Schemacop::Exceptions::ValidationError: /: Value must be a multiple of 2.
285
+ schema.validate!(-2) # => Schemacop::Exceptions::ValidationError: /: Value must have a minimum of 0.
286
+ schema.validate!(102) # => Schemacop::Exceptions::ValidationError: /: Value must have a maximum of 100.
287
+ schema.validate!(42.1) # => Schemacop::Exceptions::ValidationError: /: Invalid type, expected "integer".
288
+ schema.validate!(4r) # => Schemacop::Exceptions::ValidationError: /: Invalid type, expected "integer".
289
+ schema.validate!((4 + 0i)) # => Schemacop::Exceptions::ValidationError: /: Invalid type, expected "integer".
290
+ schema.validate!(BigDecimal(5)) # => Schemacop::Exceptions::ValidationError: /: Invalid type, expected "integer".
291
+ ```
292
+
293
+ ### Number
294
+
295
+ Type: `:number`\
296
+ DSL: `num`
297
+
298
+ The number type is used to validate various number classes. The following ruby classes
299
+ and subclasses are valid:
300
+
301
+ * `Integer`
302
+ * `Float`
303
+ * `Rational`
304
+ * `BigDecimal`
305
+
306
+ As some subclasses of `Numeric`, such as `Complex` don't support all required oeprations,
307
+ only the above list is supported. If you need support for additional number classes, please
308
+ contact the Gem maintainers.
309
+
310
+ With the various available options, validations on the value of the number can be done.
311
+
312
+ #### Options
313
+
314
+ * `minimum`
315
+ Defines an (inclusive) minimum, i.e. the number has to be equal or larger than the
316
+ given number
317
+ * `exclusive_minimum`
318
+ Defines an exclusive minimum, i.e. the number has to larger than the given number
319
+ * `maximum`
320
+ Defines an (inclusive) maximum, i.e. the number has to be equal or smaller than the
321
+ given number
322
+ * `exclusive_maximum`
323
+ Defines an exclusive maximum, i.e. the number has to smaller than the given number
324
+ * `multiple_of`
325
+ The received number has to be a multiple of the given number for the validation to
326
+ pass.
327
+
328
+ #### Examples
329
+
330
+ ```ruby
331
+ # Validates that the input is a number between 0 and 50 (inclusive) and a multiple of 0.5
332
+ schema = Schemacop::Schema3.new(:number, minimum: 0.0, maximum: (50r), multiple_of: BigDecimal('0.5'))
333
+ schema.validate!(42) # => 42
334
+ schema.validate!(42.2) # => Schemacop::Exceptions::ValidationError: /: Value must be a multiple of 0.5.
335
+ schema.validate!(-2) # => Schemacop::Exceptions::ValidationError: /: Value must have a minimum of 0.0.
336
+ schema.validate!(51) # => Schemacop::Exceptions::ValidationError: /: Value must have a maximum of 50/1.
337
+ schema.validate!(42.5) # => 42.5
338
+ schema.validate!(1.5r) # => (3/2)
339
+ schema.validate!(BigDecimal(5)) # => 0.5e1
340
+ schema.validate!((4 + 0i)) # => Schemacop::Exceptions::ValidationError: /: Invalid type, expected "big_decimal" or "float" or "integer" or "rational".
341
+ ```
342
+
343
+ ### Symbol
344
+
345
+ Type: `:symbol`\
346
+ DSL: `sym`
347
+
348
+ The symbol type is used to validate elements for the Ruby `Symbol` class.
349
+
350
+ #### Examples
351
+
352
+ ```ruby
353
+ # Validates that the input is a symbol
354
+ schema = Schemacop::Schema3.new(:symbol)
355
+ schema.validate!(:foo) # => :foo
356
+ schema.validate!('foo') # => Schemacop::Exceptions::ValidationError: /: Invalid type, expected "Symbol".
357
+ schema.validate!(123) # => Schemacop::Exceptions::ValidationError: /: Invalid type, expected "Symbol".
358
+ schema.validate!(false) # => Schemacop::Exceptions::ValidationError: /: Invalid type, expected "Symbol".
359
+ ```
360
+
361
+ ### Boolean
362
+
363
+ Type: `:boolean`\
364
+ DSL: `boo`
365
+
366
+ The boolean type is used to validate Ruby booleans, i.e. the `TrueClass` and `FalseClass`
367
+
368
+ #### Examples
369
+
370
+ ```ruby
371
+ # Validates that the input is a boolean
372
+ schema = Schemacop::Schema3.new(:boolean)
373
+ schema.validate!(true) # => true
374
+ schema.validate!(false) # => false
375
+ schema.validate!(:false) # => Schemacop::Exceptions::ValidationError: /: Invalid type, expected "boolean".
376
+ schema.validate!('false') # => Schemacop::Exceptions::ValidationError: /: Invalid type, expected "boolean".
377
+ schema.validate!(1234) # => Schemacop::Exceptions::ValidationError: /: Invalid type, expected "boolean".
378
+ ```
379
+
380
+ ### Array
381
+
382
+ Type: `:array`\
383
+ DSL: `arr`
384
+
385
+ The array type represents a ruby `Array`.
386
+ It consists of one or multiple values, which can be validated using arbitrary nodes.
387
+
388
+ #### Options
389
+
390
+ * `min_items`
391
+ This option specifies the (inclusive) minimum number of elements the array
392
+ must contain to pass the validation.
393
+
394
+ * `max_items`
395
+ This option specifies the (inclusive) maximum number of elements the array
396
+ must contain to pass the validation.
397
+
398
+ * `unique_items`
399
+ This option specifies wether the items in the array must all be distinct from
400
+ each other, or if there may be duplicate values. By default, this is false,
401
+ i.e. duplicate values are allowed
402
+
403
+ #### Contains
404
+
405
+ The `array` node features the *contains* node, which you can use with the DSL
406
+ method `cont`. With that DSL method, you can specify a schema which at least one
407
+ item in the array needs to validate against.
408
+
409
+ One use case for example could be that you want an array of integers, from which
410
+ at least one must be 5 or larger:
411
+
412
+ ```ruby
413
+ schema = Schemacop::Schema3.new :array do
414
+ list :integer
415
+ cont :integer, minimum: 5
416
+ end
417
+
418
+ schema.validate!([]) # => Schemacop::Exceptions::ValidationError: /: At least one entry must match schema {"type"=>"integer", "minimum"=>5}.
419
+ schema.validate!([1, 5]) # => [1, 5]
420
+ schema.validate!(['foo']) # => Schemacop::Exceptions::ValidationError: /[0]: Invalid type, expected "integer". /: At least one entry must match schema {"type"=>"integer", "minimum"=>5}
421
+ ```
422
+
423
+ You can also use it with the tuple validation (see below), e.g. if you want
424
+ an array of 3 integers, from which at least one needs to be 5 or larger:
425
+
426
+ ```ruby
427
+ schema = Schemacop::Schema3.new :array do
428
+ int
429
+ int
430
+ int
431
+ cont :integer, minimum: 5
432
+ end
433
+
434
+ schema.validate!([]) # => /: Array has 0 items but must have exactly 3. /: At least one entry must match schema {"type"=>"integer", "minimum"=>5}.
435
+ schema.validate!([1, 2, 3]) # => Schemacop::Exceptions::ValidationError: /: At least one entry must match schema {"type"=>"integer", "minimum"=>5}.
436
+ schema.validate!([1, 3, 5]) # => [1, 3, 5]
437
+ ```
438
+
439
+ #### Specifying properties
440
+
441
+ Array nodes support a block in which you can specify the required array contents.
442
+ The array nodes support either list validation, or tuple validation, depending on
443
+ how you specify your array contents.
444
+
445
+ ##### List validation
446
+
447
+ List validation validates a sequence of arbitrary length where each item matches
448
+ the same schema. Unless you specify a `min_items` count on the array node, an
449
+ empty array will also suffice. To specify a list validation, use the `list` DSL
450
+ method, and specify the type you want to validate against. Here, you need to
451
+ specify the type of the element using the long `type` name (e.g. `integer` and
452
+ not `int`).
453
+
454
+ For example, you can specify that you want an array with only integers between 1 and 5:
455
+
456
+ ```ruby
457
+ schema = Schemacop::Schema3.new :array do
458
+ list :integer, minimum: 1, maximum: 5
459
+ end
460
+
461
+ schema.validate!([]) # => []
462
+ schema.validate!([1, 3]) # => [1, 3]
463
+ schema.validate!([0, 6]) # => Schemacop::Exceptions::ValidationError: /[0]: Value must have a minimum of 1. /[1]: Value must have a maximum of 5.
464
+ schema.validate! ['foo'] # => Schemacop::Exceptions::ValidationError: /[0]: Invalid type, expected "integer".
465
+ ```
466
+
467
+ You can also build more complex structures, e.g. an array containing an arbitrary
468
+ number of integer arrays:
469
+
470
+ ```ruby
471
+ schema = Schemacop::Schema3.new :array do
472
+ list :array do
473
+ list :integer
474
+ end
475
+ end
476
+
477
+ schema.validate!([]) # => []
478
+ schema.validate!([[1], [2, 3]]) # => [[1], [2, 3]]
479
+ schema.validate!([['foo'], [2, 3]]) # => Schemacop::Exceptions::ValidationError: /[0]/[0]: Invalid type, expected "integer".
480
+ ```
481
+
482
+ Please note that you can only specify *one* `list` item:
483
+
484
+ ```ruby
485
+ schema = Schemacop::Schema3.new :array do
486
+ list :integer
487
+ list :string
488
+ end
489
+
490
+ # => Schemacop::Exceptions::InvalidSchemaError: You can only use "list" once.
491
+ ```
492
+
493
+ ##### Tuple validation
494
+
495
+ On the other hand, tuple validation validates a sequence of fixed length, where
496
+ each item has its own schema that it has to match. Here, the order of the items
497
+ is relevant for the validation.
498
+
499
+ For example, we want a tuple with an int, followed by a string:
500
+
501
+ ```ruby
502
+ schema = Schemacop::Schema3.new :array do
503
+ int
504
+ str
505
+ end
506
+
507
+ schema.validate!([]) # => Schemacop::Exceptions::ValidationError: /: Array has 0 items but must have exactly 2.
508
+ schema.validate!([1, 'foo']) # => [1, "foo"]
509
+ schema.validate!([1, 'foo', 'bar']) # => Schemacop::Exceptions::ValidationError: /: Array has 3 items but must have exactly 2.
510
+ ```
511
+
512
+ When using tuple validation, you can also allow additional items in the array
513
+ *after* the specified items, either with the option `additional_items` or the
514
+ DSL method `add`. With the option `additional_items` set to `true`, you can
515
+ allow any additional items:
516
+
517
+ ```ruby
518
+ schema = Schemacop::Schema3.new :array, additional_items: true do
519
+ int
520
+ str
521
+ end
522
+
523
+ schema.validate!([]) # => Schemacop::Exceptions::ValidationError: /: Array has 0 items but must have exactly 2.
524
+ schema.validate!([1, 'foo']) # => [1, "foo"]
525
+ schema.validate!([1, 'foo', 'bar']) # => [1, "foo", "bar"]
526
+ ```
527
+
528
+ You can also use the dsl method `add` to specify more exactly what type the
529
+ of the additional items may be. As with any other dsl method, you may specify
530
+ and valid schema which the additional items will be validated against:
531
+
532
+ ```ruby
533
+ schema = Schemacop::Schema3.new :array do
534
+ int
535
+ str
536
+ add :integer
537
+ end
538
+
539
+ schema.validate!([]) # => Schemacop::Exceptions::ValidationError: /: Array has 0 items but must have exactly 2.
540
+ schema.validate!([1, 'foo']) # => [1, "foo"]
541
+ schema.validate!([1, 'foo', 'bar']) # => Schemacop::Exceptions::ValidationError: /[2]: Invalid type, expected "integer".
542
+ schema.validate!([1, 'foo', 2, 3]) # => [1, "foo", 2, 3]
543
+ ```
544
+
545
+ Please note that you cannot use multiple `add` in the same array schema, this
546
+ will result in an exception:
547
+
548
+ ```ruby
549
+ schema = Schemacop::Schema3.new :array do
550
+ int
551
+ add :integer
552
+ add :string
553
+ end
554
+
555
+ # => Schemacop::Exceptions::InvalidSchemaError: You can only use "add" once to specify additional items.
556
+ ```
557
+
558
+ If you want to specify that your schema accept multiple additional types, use
559
+ the `one_of` type (see below for more infos). The correct way to specify that
560
+ you want to allow additional items, which may be an integer or a string is as
561
+ follows:
562
+
563
+ ```ruby
564
+ schema = Schemacop::Schema3.new :array do
565
+ int
566
+ add :one_of do
567
+ int
568
+ str
569
+ end
570
+ end
571
+
572
+ schema.validate!([]) # => Schemacop::Exceptions::ValidationError: /: Array has 0 items but must have exactly 1.
573
+ schema.validate!([1, 2]) # => [1, 2]
574
+ schema.validate!([1, 'foo']) # => [1, "foo"]
575
+ schema.validate!([1, :bar]) # => Schemacop::Exceptions::ValidationError: /[1]: Matches 0 definitions but should match exactly 1.
576
+ ```
577
+
578
+ ### Hash
579
+
580
+ Type: `:hash`\
581
+ DSL: `hsh`
582
+
583
+ The hash type represents a ruby `Hash` or an `object` in JSON schema language.
584
+ It consists of key-value-pairs that can be validated using arbitrary nodes.
585
+
586
+ #### Options
587
+
588
+ * `additional_properties`
589
+ This option specifies whether additional, unspecified properties are allowed
590
+ (`true`) or not (`false`). By default, this is `true` if no properties are
591
+ specified and `false` if you have specified at least one property.
592
+
593
+ * `property_names`
594
+ This option allows to specify a regexp pattern (as string) which validates the
595
+ keys of any properties that are not specified in the hash. This option only
596
+ makes sense if `additional_properties` is enabled. See below for more information.
597
+
598
+ * `min_properties`
599
+ Specifies the (inclusive) minimum number of properties a hash must contain.
600
+
601
+ * `max_properties`
602
+ Specifies the (inclusive) maximum number of properties a hash must contain.
603
+
604
+ #### Specifying properties
605
+
606
+ Hash nodes support a block in which you can specify the required hash contents.
607
+
608
+ ##### Standard properties
609
+
610
+ It supports all type nodes, but requires the suffix `?` or `!` for each
611
+ property, which specifies whether a property is required (`!`) or optional
612
+ (`?`).
613
+
614
+ ```ruby
615
+ schema = Schemacop::Schema3.new :hash do
616
+ str! :foo # Is a required property
617
+ int? :bar # Is an optional property
618
+ end
619
+
620
+ schema.validate!({}) # => Schemacop::Exceptions::ValidationError: /foo: Value must be given.
621
+ schema.validate!({foo: 'str'}) # => {"foo"=>"str"}
622
+ schema.validate!({foo: 'str', bar: 42}) # => {"foo"=>"str", "bar"=>42}
623
+ schema.validate!({bar: 42}) # => Schemacop::Exceptions::ValidationError: /foo: Value must be given.
624
+ ```
625
+
626
+ The name of the properties may either be a string or a symbol, and you can pass
627
+ in the property either identified by a symbol or a string:
628
+
629
+ The following two schemas are equal:
630
+
631
+ ```ruby
632
+ schema = Schemacop::Schema3.new :hash do
633
+ int! :foo
634
+ end
635
+
636
+ schema.validate!(foo: 42) # => {"foo"=>42}
637
+ schema.validate!('foo' => 42) # => {"foo"=>42}
638
+
639
+ schema = Schemacop::Schema3.new :hash do
640
+ int! 'foo'
641
+ end
642
+
643
+ schema.validate!(foo: 42) # => {"foo"=>42}
644
+ schema.validate!('foo' => 42) # => {"foo"=>42}
645
+ ```
646
+
647
+ The result in both cases will be a
648
+ [HashWithIndifferentAccess](https://api.rubyonrails.org/classes/ActiveSupport/HashWithIndifferentAccess.html),
649
+ which means that you can access the data in the hash with the symbol as well as
650
+ the string representation:
651
+
652
+ ```ruby
653
+ schema = Schemacop::Schema3.new :hash do
654
+ int! :foo
655
+ end
656
+
657
+ result = schema.validate!(foo: 42)
658
+
659
+ result.class # => ActiveSupport::HashWithIndifferentAccess
660
+ result[:foo] # => 42
661
+ result['foo'] # 42
662
+ ```
663
+
664
+ Please note that if you specify the value twice in the data you want to
665
+ validate, once with the key being a symbol and once being a string, Schemacop
666
+ will raise an error:
667
+
668
+ ```ruby
669
+ schema = Schemacop::Schema3.new :hash do
670
+ int! :foo
671
+ end
672
+
673
+ schema.validate!(foo: 42, 'foo' => 43) # => Schemacop::Exceptions::ValidationError: /: Has 1 ambiguous properties: [:foo].
674
+ ```
675
+
676
+ ##### Pattern properties
677
+
678
+ In addition to symbols, property keys can also be a regular expression. Here,
679
+ you may only use the optional `?` suffix for the property. This allows any
680
+ property, which matches the type and the name of the property matches the
681
+ regular expression.
682
+
683
+ ```ruby
684
+ schema = Schemacop::Schema3.new :hash do
685
+ # The following statement allows any number of integer properties of which the
686
+ # name starts with `id_`.
687
+ int? /^id_.*$/
688
+ end
689
+
690
+ schema.validate!({}) # => {}
691
+ schema.validate!({id_foo: 1}) # => {"id_foo"=>1}
692
+ schema.validate!({id_foo: 1, id_bar: 2}) # => {"id_foo"=>1, "id_bar"=>2}
693
+ schema.validate!({foo: 3}) # => Schemacop::Exceptions::ValidationError: /: Obsolete property "foo".
694
+ ```
695
+
696
+ ##### Additional properties & property names
697
+
698
+ In addition to standard properties, you can allow the hash to contain
699
+ additional, unspecified properties. By default, this is turned off if you have
700
+ defined at least one standard property.
701
+
702
+ When it comes to additional properties, you have the choice to either just
703
+ enable all of them by enabling the option `additional_properties`:
704
+
705
+ ```ruby
706
+ # This schema will accept any additional properties
707
+ schema = Schemacop::Schema3.new :hash, additional_properties: true
708
+
709
+ schema.validate!({}) # => {}
710
+ schema.validate!({foo: :bar, baz: 42}) # => {"foo"=>:bar, "baz"=>42}
711
+ ```
712
+
713
+ Using the DSL method `add` in the hash-node's body however, you can specify
714
+ an additional schema to which additional properties must adhere:
715
+
716
+
717
+ ```ruby
718
+ Schemacop::Schema3.new :hash do
719
+ int! :id
720
+
721
+ # Allow any additional properties besides `id`, but their value must be a
722
+ # string.
723
+ add :string
724
+ end
725
+
726
+ schema.validate!({id: 1}) # => {"id"=>1}
727
+ schema.validate!({id: 1, foo: 'bar'}) # => {"id"=>1, "foo"=>"bar"}
728
+ schema.validate!({id: 1, foo: 42}) # => Schemacop::Exceptions::ValidationError: /foo: Invalid type, expected "string".
729
+ ```
730
+
731
+ Using the option `property_names`, you can additionaly specify a pattern that
732
+ any additional property **keys** must adhere to:
733
+
734
+ ```ruby
735
+ # The following schema allows any number of properties, but all keys must
736
+ # consist of downcase letters from a-z.
737
+ schema = Schemacop::Schema3.new :hash, additional_properties: true, property_names: '^[a-z]+$'
738
+
739
+
740
+ schema.validate!({}) # => {}
741
+ schema.validate!({foo: 123}) # => {"foo"=>123}
742
+ schema.validate!({Foo: 'bar'}) # => Schemacop::Exceptions::ValidationError: /: Property name "Foo" does not match "^[a-z]+$".
743
+
744
+ # The following schema allows any number of properties, but all keys must
745
+ # consist of downcase letters from a-z AND the properties must be arrays.
746
+ schema = Schemacop::Schema3.new :hash, additional_properties: true, property_names: '^[a-z]+$' do
747
+ add :array
748
+ end
749
+
750
+ schema.validate!({}) # => {}
751
+ schema.validate!({foo: [1, 2, 3]}) # => {"foo"=>[1, 2, 3]}
752
+ schema.validate!({foo: :bar}) # => Schemacop::Exceptions::ValidationError: /foo: Invalid type, expected "array".
753
+ schema.validate!({Foo: :bar}) # => Schemacop::Exceptions::ValidationError: /: Property name :Foo does not match "^[a-z]+$". /Foo: Invalid type, expected "array".
754
+ ```
755
+
756
+ ##### Dependencies
757
+
758
+ Using the DSL method `dep`, you can specifiy (non-nested) property dependencies:
759
+
760
+ ```ruby
761
+ # In this example, `billing_address` and `phone_number` are required if
762
+ # `credit_card` is given, and `credit_card` is required if `billing_address` is
763
+ # given.
764
+ schema = Schemacop::Schema3.new :hash do
765
+ str! :name
766
+ str? :credit_card
767
+ str? :billing_address
768
+ str? :phone_number
769
+
770
+ dep :credit_card, :billing_address, :phone_number
771
+ dep :billing_address, :credit_card
772
+ end
773
+
774
+ schema.validate!({}) # => Schemacop::Exceptions::ValidationError: /name: Value must be given.
775
+ schema.validate!({name: 'Joe Doe'}) # => {"name"=>"Joe Doe"}
776
+ schema.validate!({
777
+ name: 'Joe Doe',
778
+ billing_address: 'Street 42'
779
+ })
780
+ # => Schemacop::Exceptions::ValidationError: /: Missing property "credit_card" because "billing_address" is given.
781
+
782
+ schema.validate!({
783
+ name: 'Joe Doe',
784
+ credit_card: 'XXXX XXXX XXXX XXXX X'
785
+ })
786
+ # => Schemacop::Exceptions::ValidationError: /: Missing property "billing_address" because "credit_card" is given. /: Missing property "phone_number" because "credit_card" is given.
787
+
788
+ schema.validate!({
789
+ name: 'Joe Doe',
790
+ billing_address: 'Street 42',
791
+ phone_number: '000-000-00-00',
792
+ credit_card: 'XXXX XXXX XXXX XXXX X'
793
+ })
794
+ # => {"name"=>"Joe Doe", "credit_card"=>"XXXX XXXX XXXX XXXX X", "billing_address"=>"Street 42", "phone_number"=>"000-000-00-00"}
795
+ ```
796
+
797
+ ### Object
798
+
799
+ Type: `:object`\
800
+ DSL: `obj`
801
+
802
+ The object type represents a Ruby `Object`. Please note that the `as_json`
803
+ method on nodes of this type will just return `{}` (an empty JSON object), as
804
+ there isn't a useful way to represent a Ruby object without conflicting with the
805
+ `Hash` type. If you want to represent a JSON object, you should use the `Hash`
806
+ node.
807
+
808
+ In the most basic form, this node will accept anything:
809
+
810
+ ```ruby
811
+ schema = Schemacop::Schema3.new :object
812
+
813
+ schema.validate!(nil) # => nil
814
+ schema.validate!(true) # => true
815
+ schema.validate!(false) # => false
816
+ schema.validate!(Object.new) # => #<Object:0x0000556ab4f58dd0>
817
+ schema.validate!('foo') # => "foo"
818
+ ```
819
+
820
+ If you want to limit the allowed classes, you can so so by specifying an array
821
+ of allowed classes:
822
+
823
+ ```ruby
824
+ schema = Schemacop::Schema3.new :object, classes: [String]
825
+
826
+ schema.validate!(nil) # => nil
827
+ schema.validate!(true) # => Schemacop::Exceptions::ValidationError: /: Invalid type, expected "String".
828
+ schema.validate!(Object.new) # => Schemacop::Exceptions::ValidationError: /: Invalid type, expected "String".
829
+ schema.validate!('foo') # => "foo"
830
+ schema.validate!('foo'.html_safe) # => Schemacop::Exceptions::ValidationError: /: Invalid type, expected "String".
831
+ ```
832
+
833
+ Here, the node checks if the given value is an instance of any of the given
834
+ classes with `instance_of?`, i.e. the exact class and not a subclass.
835
+
836
+ If you want to allow subclasses, you can specify this by using the `strict` option:
837
+
838
+ ```ruby
839
+ schema = Schemacop::Schema3.new :object, classes: [String], strict: false
840
+
841
+ schema.validate!(nil) # => nil
842
+ schema.validate!(true) # => Schemacop::Exceptions::ValidationError: /: Invalid type, expected "String".
843
+ schema.validate!(Object.new) # => Schemacop::Exceptions::ValidationError: /: Invalid type, expected "String".
844
+ schema.validate!('foo') # => "foo"
845
+ schema.validate!('foo'.html_safe) # => "foo"
846
+ ```
847
+
848
+ If you set the `strict` option to `false`, the check is done using `is_a?` instead of
849
+ `instance_of?`, which also allows subclasses
850
+
851
+ ### AllOf
852
+
853
+ Type: `:all_of`\
854
+ DSL: `all_of`
855
+
856
+ With the AllOf node you can specify multiple schemas, for which the given value
857
+ needs to validate against every one:
858
+
859
+ ```ruby
860
+ schema = Schemacop::Schema3.new :all_of do
861
+ str min_length: 2
862
+ str max_length: 4
863
+ end
864
+
865
+ schema.validate!('foo') # => "foo"
866
+ schema.validate!('foooo') # => Schemacop::Exceptions::ValidationError: /: Does not match all allOf conditions.
867
+ ```
868
+
869
+ Please note that it's possible to create nonsensical schemas with this node, as
870
+ you can combine multiple schemas which contradict each other:
871
+
872
+ ```ruby
873
+ schema = Schemacop::Schema3.new :all_of do
874
+ str min_length: 4
875
+ str max_length: 1
876
+ end
877
+
878
+ schema.validate!('foo') # => Schemacop::Exceptions::ValidationError: /: Does not match all allOf conditions.
879
+ schema.validate!('foooo') # => Schemacop::Exceptions::ValidationError: /: Does not match all allOf conditions.
880
+ ```
881
+
882
+ ### AnyOf
883
+
884
+ Type: `:any_of`\
885
+ DSL: `any_of`
886
+
887
+ Similar to the `all_of` node, you can specify multiple schemas, for which the
888
+ given value needs to validate against at least one of the schemas.
889
+
890
+ For example, your value needs to be either a string which is at least 2
891
+ characters long, or an integer:
892
+
893
+ ```ruby
894
+ schema = Schemacop::Schema3.new :any_of do
895
+ str min_length: 2
896
+ int
897
+ end
898
+
899
+ schema.validate!('f') # => Schemacop::Exceptions::ValidationError: /: Does not match any anyOf condition.
900
+ schema.validate!('foo') # => "foo"
901
+ schema.validate!(42) # => 42
902
+ ```
903
+
904
+ Please note that you need to specify at least one item in the `any_of` node:
905
+
906
+ ```ruby
907
+ Schemacop::Schema3.new :any_of # => Schemacop::Exceptions::InvalidSchemaError: Node "any_of" makes only sense with at least 1 item.
908
+ ```
909
+
910
+ ### OneOf
911
+
912
+ Type: `:one_of`\
913
+ DSL: `one_of`
914
+
915
+ Similar to the `all_of` node, you can specify multiple schemas, for which the
916
+ given value needs to validate against exaclty one of the schemas. If the given
917
+ value validates against multiple schemas, the value is invalid.
918
+
919
+ For example, if you want an integer which is either a multiple of 2 or 3,
920
+ but not both (i.e. no multiple of 6), you could do it as follows:
921
+
922
+ ```ruby
923
+ schema = Schemacop::Schema3.new :one_of do
924
+ int multiple_of: 2
925
+ int multiple_of: 3
926
+ end
927
+
928
+ schema.validate!(2) # => 2
929
+ schema.validate!(3) # => 3
930
+ schema.validate!(4) # => 4
931
+ schema.validate!(5) # => Schemacop::Exceptions::ValidationError: /: Matches 0 definitions but should match exactly 1.
932
+ schema.validate!(6) # => Schemacop::Exceptions::ValidationError: /: Matches 2 definitions but should match exactly 1.
933
+ ```
934
+
935
+ Again, as previously with the AllOf node, you're allowed to create schemas
936
+ which will not work for any input, e.g. by specifying the same schema twice:
937
+
938
+ ```ruby
939
+ schema = Schemacop::Schema3.new :one_of do
940
+ int multiple_of: 2
941
+ int multiple_of: 2
942
+ end
943
+
944
+ schema.validate!(2) # => Schemacop::Exceptions::ValidationError: /: Matches 2 definitions but should match exactly 1.
945
+ schema.validate!(3) # => Schemacop::Exceptions::ValidationError: /: Matches 0 definitions but should match exactly 1.
946
+ schema.validate!(4) # => Schemacop::Exceptions::ValidationError: /: Matches 2 definitions but should match exactly 1.
947
+ schema.validate!(5) # => Schemacop::Exceptions::ValidationError: /: Matches 0 definitions but should match exactly 1.
948
+ schema.validate!(6) # => Schemacop::Exceptions::ValidationError: /: Matches 2 definitions but should match exactly 1.
949
+ ```
950
+
951
+ ### IsNot
952
+
953
+ Type: `:is_not`\
954
+ DSL: `is_not`
955
+
956
+ With the `is_not` node, you can specify a schema which the given value must not
957
+ validate against, i.e. every value which matches the schema will make this node
958
+ invalid.
959
+
960
+ For example, you want anything but the numbers between 3 and 5:
961
+
962
+ ```ruby
963
+ schema = Schemacop::Schema3.new :is_not do
964
+ int minimum: 3, maximum: 5
965
+ end
966
+
967
+ schema.validate!(nil) # => nil
968
+ schema.validate!(1) # => 1
969
+ schema.validate!(2) # => 2
970
+ schema.validate!(3) # => Schemacop::Exceptions::ValidationError: /: Must not match schema: {"type"=>"integer", "minimum"=>3, "maximum"=>5}.
971
+ schema.validate!('foo') # => "foo"
972
+ ```
973
+
974
+ Note that a `is_not` node needs exactly one item:
975
+
976
+ ```ruby
977
+ schema = Schemacop::Schema3.new :is_not # => Schemacop::Exceptions::InvalidSchemaError: Node "is_not" only allows exactly one item.
978
+ ```
979
+
980
+ ### Reference
981
+
982
+ **Referencing**
983
+ DSL: `ref`\
984
+ Type: `reference`
985
+
986
+ **Definition**
987
+ DSL: `scm`
988
+
989
+ Finally, with the *Reference* node, you can define schemas and then later reference
990
+ them for usage, e.g. when you have a rather long schema which you need at multiple
991
+ places.
992
+
993
+ #### Examples
994
+
995
+ For example, let's define an object with an schema called `Address`, which we'll
996
+ reference multiple times:
997
+
998
+ ```ruby
999
+ schema = Schemacop::Schema3.new :hash do
1000
+ scm :Address do
1001
+ str! :street
1002
+ str! :zip_code
1003
+ str! :location
1004
+ str! :country
1005
+ end
1006
+
1007
+ ref! :shipping_address, :Address
1008
+ ref! :billing_address, :Address
1009
+ end
1010
+
1011
+ schema.validate!({}) # => Schemacop::Exceptions::ValidationError: /shipping_address: Value must be given. /billing_address: Value must be given.
1012
+ schema.validate!({
1013
+ shipping_address: 'foo',
1014
+ billing_address: 42
1015
+ })
1016
+ # => Schemacop::Exceptions::ValidationError: /shipping_address: Invalid type, expected "object". /billing_address: Invalid type, expected "object".
1017
+
1018
+ schema.validate!({
1019
+ shipping_address: {
1020
+ street: 'Example Street 42',
1021
+ zip_code: '12345',
1022
+ location: 'London',
1023
+ country: 'United Kingdom'
1024
+ },
1025
+ billing_address: {
1026
+ street: 'Main St.',
1027
+ zip_code: '54321',
1028
+ location: 'Washington DC',
1029
+ country: 'USA'
1030
+ }
1031
+ })
1032
+
1033
+ # => {"shipping_address"=>{"street"=>"Example Street 42", "zip_code"=>"12345", "location"=>"London", "country"=>"United Kingdom"}, "billing_address"=>{"street"=>"Main St.", "zip_code"=>"54321", "location"=>"Washington DC", "country"=>"USA"}}
1034
+ ```
1035
+
1036
+ Note that if you use the reference node with the long type name `reference`,
1037
+ e.g. in an array, you need to specify the "name" of the schema in the
1038
+ `path` option:
1039
+
1040
+ ```ruby
1041
+ schema = Schemacop::Schema3.new :array do
1042
+ scm :User do
1043
+ str! :first_name
1044
+ str! :last_name
1045
+ end
1046
+
1047
+ list :reference, path: :User
1048
+ end
1049
+
1050
+ schema.validate!([]) # => []
1051
+ schema.validate!([{first_name: 'Joe', last_name: 'Doe'}]) # => [{"first_name"=>"Joe", "last_name"=>"Doe"}]
1052
+ schema.validate!([id: 42, first_name: 'Joe']) # => Schemacop::Exceptions::ValidationError: /[0]/last_name: Value must be given. /[0]: Obsolete property "id".
1053
+ ```
1054
+
1055
+ ## Context
1056
+
1057
+ Schemacop also features the concept of a `Context`. You can define schemas in a
1058
+ context, and then reference them in other schemas in that context. This is e.g.
1059
+ useful if you need a part of the schema to be different depending on the
1060
+ business action.
1061
+
1062
+ Examples:
1063
+
1064
+ ```ruby
1065
+ # Define a new context
1066
+ context = Schemacop::V3::Context.new
1067
+
1068
+ # Define the :Person schema in that context
1069
+ context.schema :Person do
1070
+ str! :first_name
1071
+ str! :last_name
1072
+ ref? :info, :PersonInfo
1073
+ end
1074
+
1075
+ # And also define the :PersonInfo schema in that context
1076
+ context.schema :PersonInfo do
1077
+ str! :born_at, format: :date
1078
+ end
1079
+
1080
+ # Now we can define our general schema, where we reference the :Person schema.
1081
+ # Note that at this point, we don't know what's in the :Person schema.
1082
+ schema = Schemacop::Schema3.new :reference, path: :Person
1083
+
1084
+ # Validate the data in the context we defined before, where we need the first_name
1085
+ # and last_name of a person, as well as an optional info hash with the born_at date
1086
+ # of the person.
1087
+ Schemacop.with_context context do
1088
+ schema.validate!({first_name: 'Joe', last_name: 'Doe', info: { born_at: '1980-01-01'} })
1089
+ # => {"first_name"=>"Joe", "last_name"=>"Doe", "info"=>{"born_at"=>Tue, 01 Jan 1980}}
1090
+ end
1091
+
1092
+ # Now we might want another context, where the person is more anonymous, and as
1093
+ # such, we need another schema
1094
+ other_context = Schemacop::V3::Context.new
1095
+
1096
+ # Here, we only want the nickname of the person
1097
+ other_context.schema :Person do
1098
+ str! :nickname
1099
+ end
1100
+
1101
+ # Finally, validate the data in the new context. We do not want the real name or
1102
+ # birth date of the person, instead only the nickname is allowed.
1103
+ Schemacop.with_context other_context do
1104
+ schema.validate!({first_name: 'Joe', last_name: 'Doe', info: { born_at: '1980-01-01'} })
1105
+ # => Schemacop::Exceptions::ValidationError: /nickname: Value must be given.
1106
+ # /: Obsolete property "first_name".
1107
+ # /: Obsolete property "last_name".
1108
+ # /: Obsolete property "info".
1109
+
1110
+ schema.validate!({nickname: 'J.'}) # => {"nickname"=>"J."}
1111
+ end
1112
+ ```
1113
+
1114
+ As one can see, we validated the data against the same schema, but because we
1115
+ defined the referenced schemas differently in the two contexts, we were able
1116
+ to use other data in the second context than in the first.
1117
+
1118
+ ## External schemas
1119
+
1120
+ Finally, Schemacop features the possibility to specify schemas in seperate
1121
+ files. This is especially useful is you have schemas in your application which
1122
+ are used multiple times throughout the application.
1123
+
1124
+ For each schema, you define the schema in a separate file, and after loading the
1125
+ schemas, you can reference them in other schemas.
1126
+
1127
+ The default load path is `'app/schemas'`, but this can be configured by setting
1128
+ the value of the `load_paths` attribute of the `Schemacop` module.
1129
+
1130
+ Please note that the following predescence order is used for the schemas:
1131
+
1132
+ ```
1133
+ local schemas > context schemas > global schemas
1134
+ ```
1135
+
1136
+ Where:
1137
+
1138
+ * local schemas: Defined by using the DSL method? `scm`
1139
+ * context schemas: Defined in the current context using `context.schema`
1140
+ * global schemas: Defined in a ruby file in the load path
1141
+
1142
+ ### Rails applications
1143
+
1144
+ In Rails applications, your schemas are automatically eager-laoded from the load
1145
+ path `'app/schemas'` when your application is started.
1146
+
1147
+ After starting your application, you can reference them like normally defined
1148
+ reference schemas, with the name being relative to the load path.
1149
+
1150
+ Example:
1151
+
1152
+ You defined the following two schemas in the `'app/schemas'` directory:
1153
+
1154
+ ```ruby
1155
+ # app/schemas/user.rb
1156
+ schema :hash do
1157
+ str! :first_name
1158
+ str! :last_name
1159
+ ary? :groups do
1160
+ list :reference, path: 'nested/group'
1161
+ end
1162
+ end
1163
+ ```
1164
+
1165
+ ```ruby
1166
+ # app/schemas/nested/user.rb
1167
+ schema :hash do
1168
+ str! :name
1169
+ end
1170
+ ```
1171
+
1172
+ To use the schema, you then can simply reference the schema as with normal
1173
+ reference schemas:
1174
+
1175
+ ```ruby
1176
+ schema = Schemacop::Schema3.new :hash do
1177
+ ref! :usr, :user
1178
+ end
1179
+
1180
+ schema.validate!({usr: {first_name: 'Joe', last_name: 'Doe'}})
1181
+ # => {"usr"=>{"first_name"=>"Joe", "last_name"=>"Doe"}}
1182
+
1183
+ schema.validate!({usr: {first_name: 'Joe', last_name: 'Doe', groups: []}})
1184
+ # => {"usr"=>{"first_name"=>"Joe", "last_name"=>"Doe", "groups"=>[]}}
1185
+
1186
+ schema.validate!({usr: {first_name: 'Joe', last_name: 'Doe', groups: [{name: 'foo'}, {name: 'bar'}]}})
1187
+ # => {"usr"=>{"first_name"=>"Joe", "last_name"=>"Doe", "groups"=>[{"name"=>"foo"}, {"name"=>"bar"}]}}
1188
+ ```
1189
+
1190
+ ### Non-Rails applications
1191
+
1192
+ Usage in non-Rails applications is the same as with usage in Rails applications,
1193
+ however you need to eager load the schemas yourself:
1194
+
1195
+ ```ruby
1196
+ Schemacop::V3::GlobalContext.eager_load!
1197
+ ```