schemacop 2.4.6 → 3.0.0.rc3

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