schemacop 2.4.5 → 3.0.0.rc2

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