schemacop 2.4.7 → 3.0.0.rc4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (173) 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 +37 -0
  7. data/README.md +53 -710
  8. data/README_V2.md +775 -0
  9. data/README_V3.md +1253 -0
  10. data/Rakefile +8 -12
  11. data/VERSION +1 -1
  12. data/lib/schemacop.rb +35 -37
  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} +17 -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/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 +267 -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 +219 -0
  52. data/lib/schemacop/v3/node_registry.rb +45 -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 +55 -0
  58. data/lib/schemacop/v3/result.rb +58 -0
  59. data/lib/schemacop/v3/string_node.rb +132 -0
  60. data/lib/schemacop/v3/symbol_node.rb +13 -0
  61. data/schemacop.gemspec +24 -27
  62. data/test/lib/test_helper.rb +167 -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 +157 -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 +106 -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 +166 -0
  90. data/test/unit/schemacop/v3/hash_node_test.rb +972 -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 +162 -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 +367 -0
  98. data/test/unit/schemacop/v3/string_node_test.rb +372 -0
  99. data/test/unit/schemacop/v3/symbol_node_test.rb +75 -0
  100. metadata +151 -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 -118
  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
data/README_V3.md ADDED
@@ -0,0 +1,1253 @@
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 :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
+ 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: `ary`
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 `false`, i.e. you need to
591
+ explicitly set it to `true` if you want to allow arbitrary additional properties,
592
+ or use the `add` DSL method (see below) to specify additional properties.
593
+
594
+ * `property_names`
595
+ This option allows to specify a regexp pattern (as string) which validates the
596
+ keys of any properties that are not specified in the hash. This option only
597
+ makes sense if `additional_properties` is enabled. See below for more information.
598
+
599
+ * `min_properties`
600
+ Specifies the (inclusive) minimum number of properties a hash must contain.
601
+
602
+ * `max_properties`
603
+ Specifies the (inclusive) maximum number of properties a hash must contain.
604
+
605
+ #### Specifying properties
606
+
607
+ Hash nodes support a block in which you can specify the required hash contents.
608
+
609
+ ##### Standard properties
610
+
611
+ It supports all type nodes, but requires the suffix `?` or `!` for each
612
+ property, which specifies whether a property is required (`!`) or optional
613
+ (`?`).
614
+
615
+ ```ruby
616
+ schema = Schemacop::Schema3.new :hash do
617
+ str! :foo # Is a required property
618
+ int? :bar # Is an optional property
619
+ end
620
+
621
+ schema.validate!({}) # => Schemacop::Exceptions::ValidationError: /foo: Value must be given.
622
+ schema.validate!({foo: 'str'}) # => {"foo"=>"str"}
623
+ schema.validate!({foo: 'str', bar: 42}) # => {"foo"=>"str", "bar"=>42}
624
+ schema.validate!({bar: 42}) # => Schemacop::Exceptions::ValidationError: /foo: Value must be given.
625
+ ```
626
+
627
+ The name of the properties may either be a string or a symbol, and you can pass
628
+ in the property either identified by a symbol or a string:
629
+
630
+ The following two schemas are equal:
631
+
632
+ ```ruby
633
+ schema = Schemacop::Schema3.new :hash do
634
+ int! :foo
635
+ end
636
+
637
+ schema.validate!(foo: 42) # => {"foo"=>42}
638
+ schema.validate!('foo' => 42) # => {"foo"=>42}
639
+
640
+ schema = Schemacop::Schema3.new :hash do
641
+ int! 'foo'
642
+ end
643
+
644
+ schema.validate!(foo: 42) # => {"foo"=>42}
645
+ schema.validate!('foo' => 42) # => {"foo"=>42}
646
+ ```
647
+
648
+ The result in both cases will be a
649
+ [HashWithIndifferentAccess](https://api.rubyonrails.org/classes/ActiveSupport/HashWithIndifferentAccess.html),
650
+ which means that you can access the data in the hash with the symbol as well as
651
+ the string representation:
652
+
653
+ ```ruby
654
+ schema = Schemacop::Schema3.new :hash do
655
+ int! :foo
656
+ end
657
+
658
+ result = schema.validate!(foo: 42)
659
+
660
+ result.class # => ActiveSupport::HashWithIndifferentAccess
661
+ result[:foo] # => 42
662
+ result['foo'] # 42
663
+ ```
664
+
665
+ Please note that if you specify the value twice in the data you want to
666
+ validate, once with the key being a symbol and once being a string, Schemacop
667
+ will raise an error:
668
+
669
+ ```ruby
670
+ schema = Schemacop::Schema3.new :hash do
671
+ int! :foo
672
+ end
673
+
674
+ schema.validate!(foo: 42, 'foo' => 43) # => Schemacop::Exceptions::ValidationError: /: Has 1 ambiguous properties: [:foo].
675
+ ```
676
+
677
+ In addition to the normal node options (which vary from type to type, check
678
+ the respective nodes for details), properties also support the `as` option.
679
+
680
+ With this, you can "rename" properties in the output:
681
+
682
+ ```ruby
683
+ schema = Schemacop::Schema3.new :hash do
684
+ int! :foo, as: :bar
685
+ end
686
+
687
+ schema.validate!({foo: 42}) # => {"bar"=>42}
688
+ ```
689
+
690
+ Please note that if you specify a node with the same property name multiple
691
+ times, or use the `as` option to rename a node to the same name of another
692
+ node, the last specified node will be used:
693
+
694
+ ```ruby
695
+ schema = Schemacop::Schema3.new :hash do
696
+ int? :foo
697
+ str? :foo
698
+ end
699
+
700
+ schema.validate!({foo: 1}) # => Schemacop::Exceptions::ValidationError: /foo: Invalid type, expected "string".
701
+ schema.validate!({foo: 'bar'}) # => {"foo"=>"bar"}
702
+ ```
703
+
704
+ As well as:
705
+
706
+ ```ruby
707
+ schema = Schemacop::Schema3.new :hash do
708
+ int? :foo
709
+ int? :bar, as: :foo
710
+ end
711
+
712
+ schema.validate!({foo: 1}) # => {"foo"=>1}
713
+ schema.validate!({foo: 1, bar: 2}) # => {"foo"=>2}
714
+ schema.validate!({bar: 2}) # => {"foo"=>2}
715
+ ```
716
+
717
+ If you want to specify a node which may be one of multiple types, use the `one_of`
718
+ node (see further down for more details):
719
+
720
+ ```ruby
721
+ schema = Schemacop::Schema3.new :hash do
722
+ one_of! :foo do
723
+ int
724
+ str
725
+ end
726
+ end
727
+
728
+ schema.validate!({foo: 1}) # => {"foo"=>1}
729
+ schema.validate!({foo: 'bar'}) # => {"foo"=>"bar"}
730
+ ```
731
+
732
+ ##### Pattern properties
733
+
734
+ In addition to symbols, property keys can also be a regular expression. Here,
735
+ you may only use the optional `?` suffix for the property. This allows any
736
+ property, which matches the type and the name of the property matches the
737
+ regular expression.
738
+
739
+ ```ruby
740
+ schema = Schemacop::Schema3.new :hash do
741
+ # The following statement allows any number of integer properties of which the
742
+ # name starts with `id_`.
743
+ int? /^id_.*$/
744
+ end
745
+
746
+ schema.validate!({}) # => {}
747
+ schema.validate!({id_foo: 1}) # => {"id_foo"=>1}
748
+ schema.validate!({id_foo: 1, id_bar: 2}) # => {"id_foo"=>1, "id_bar"=>2}
749
+ schema.validate!({foo: 3}) # => Schemacop::Exceptions::ValidationError: /: Obsolete property "foo".
750
+ ```
751
+
752
+ ##### Additional properties & property names
753
+
754
+ In addition to standard properties, you can allow the hash to contain
755
+ additional, unspecified properties. By default, this is turned off if you have
756
+ defined at least one standard property.
757
+
758
+ When it comes to additional properties, you have the choice to either just
759
+ enable all of them by enabling the option `additional_properties`:
760
+
761
+ ```ruby
762
+ # This schema will accept any additional properties
763
+ schema = Schemacop::Schema3.new :hash, additional_properties: true
764
+
765
+ schema.validate!({}) # => {}
766
+ schema.validate!({foo: :bar, baz: 42}) # => {"foo"=>:bar, "baz"=>42}
767
+ ```
768
+
769
+ Using the DSL method `add` in the hash-node's body however, you can specify
770
+ an additional schema to which additional properties must adhere:
771
+
772
+
773
+ ```ruby
774
+ Schemacop::Schema3.new :hash do
775
+ int! :id
776
+
777
+ # Allow any additional properties besides `id`, but their value must be a
778
+ # string.
779
+ add :string
780
+ end
781
+
782
+ schema.validate!({id: 1}) # => {"id"=>1}
783
+ schema.validate!({id: 1, foo: 'bar'}) # => {"id"=>1, "foo"=>"bar"}
784
+ schema.validate!({id: 1, foo: 42}) # => Schemacop::Exceptions::ValidationError: /foo: Invalid type, expected "string".
785
+ ```
786
+
787
+ Using the option `property_names`, you can additionaly specify a pattern that
788
+ any additional property **keys** must adhere to:
789
+
790
+ ```ruby
791
+ # The following schema allows any number of properties, but all keys must
792
+ # consist of downcase letters from a-z.
793
+ schema = Schemacop::Schema3.new :hash, additional_properties: true, property_names: '^[a-z]+$'
794
+
795
+
796
+ schema.validate!({}) # => {}
797
+ schema.validate!({foo: 123}) # => {"foo"=>123}
798
+ schema.validate!({Foo: 'bar'}) # => Schemacop::Exceptions::ValidationError: /: Property name "Foo" does not match "^[a-z]+$".
799
+
800
+ # The following schema allows any number of properties, but all keys must
801
+ # consist of downcase letters from a-z AND the properties must be arrays.
802
+ schema = Schemacop::Schema3.new :hash, additional_properties: true, property_names: '^[a-z]+$' do
803
+ add :array
804
+ end
805
+
806
+ schema.validate!({}) # => {}
807
+ schema.validate!({foo: [1, 2, 3]}) # => {"foo"=>[1, 2, 3]}
808
+ schema.validate!({foo: :bar}) # => Schemacop::Exceptions::ValidationError: /foo: Invalid type, expected "array".
809
+ schema.validate!({Foo: :bar}) # => Schemacop::Exceptions::ValidationError: /: Property name :Foo does not match "^[a-z]+$". /Foo: Invalid type, expected "array".
810
+ ```
811
+
812
+ ##### Dependencies
813
+
814
+ Using the DSL method `dep`, you can specifiy (non-nested) property dependencies:
815
+
816
+ ```ruby
817
+ # In this example, `billing_address` and `phone_number` are required if
818
+ # `credit_card` is given, and `credit_card` is required if `billing_address` is
819
+ # given.
820
+ schema = Schemacop::Schema3.new :hash do
821
+ str! :name
822
+ str? :credit_card
823
+ str? :billing_address
824
+ str? :phone_number
825
+
826
+ dep :credit_card, :billing_address, :phone_number
827
+ dep :billing_address, :credit_card
828
+ end
829
+
830
+ schema.validate!({}) # => Schemacop::Exceptions::ValidationError: /name: Value must be given.
831
+ schema.validate!({name: 'Joe Doe'}) # => {"name"=>"Joe Doe"}
832
+ schema.validate!({
833
+ name: 'Joe Doe',
834
+ billing_address: 'Street 42'
835
+ })
836
+ # => Schemacop::Exceptions::ValidationError: /: Missing property "credit_card" because "billing_address" is given.
837
+
838
+ schema.validate!({
839
+ name: 'Joe Doe',
840
+ credit_card: 'XXXX XXXX XXXX XXXX X'
841
+ })
842
+ # => Schemacop::Exceptions::ValidationError: /: Missing property "billing_address" because "credit_card" is given. /: Missing property "phone_number" because "credit_card" is given.
843
+
844
+ schema.validate!({
845
+ name: 'Joe Doe',
846
+ billing_address: 'Street 42',
847
+ phone_number: '000-000-00-00',
848
+ credit_card: 'XXXX XXXX XXXX XXXX X'
849
+ })
850
+ # => {"name"=>"Joe Doe", "credit_card"=>"XXXX XXXX XXXX XXXX X", "billing_address"=>"Street 42", "phone_number"=>"000-000-00-00"}
851
+ ```
852
+
853
+ ### Object
854
+
855
+ Type: `:object`\
856
+ DSL: `obj`
857
+
858
+ The object type represents a Ruby `Object`. Please note that the `as_json`
859
+ method on nodes of this type will just return `{}` (an empty JSON object), as
860
+ there isn't a useful way to represent a Ruby object without conflicting with the
861
+ `Hash` type. If you want to represent a JSON object, you should use the `Hash`
862
+ node.
863
+
864
+ In the most basic form, this node will accept anything:
865
+
866
+ ```ruby
867
+ schema = Schemacop::Schema3.new :object
868
+
869
+ schema.validate!(nil) # => nil
870
+ schema.validate!(true) # => true
871
+ schema.validate!(false) # => false
872
+ schema.validate!(Object.new) # => #<Object:0x0000556ab4f58dd0>
873
+ schema.validate!('foo') # => "foo"
874
+ ```
875
+
876
+ If you want to limit the allowed classes, you can so so by specifying an array
877
+ of allowed classes:
878
+
879
+ ```ruby
880
+ schema = Schemacop::Schema3.new :object, classes: [String]
881
+
882
+ schema.validate!(nil) # => nil
883
+ schema.validate!(true) # => Schemacop::Exceptions::ValidationError: /: Invalid type, expected "String".
884
+ schema.validate!(Object.new) # => Schemacop::Exceptions::ValidationError: /: Invalid type, expected "String".
885
+ schema.validate!('foo') # => "foo"
886
+ schema.validate!('foo'.html_safe) # => Schemacop::Exceptions::ValidationError: /: Invalid type, expected "String".
887
+ ```
888
+
889
+ Here, the node checks if the given value is an instance of any of the given
890
+ classes with `instance_of?`, i.e. the exact class and not a subclass.
891
+
892
+ If you want to allow subclasses, you can specify this by using the `strict` option:
893
+
894
+ ```ruby
895
+ schema = Schemacop::Schema3.new :object, classes: [String], strict: false
896
+
897
+ schema.validate!(nil) # => nil
898
+ schema.validate!(true) # => Schemacop::Exceptions::ValidationError: /: Invalid type, expected "String".
899
+ schema.validate!(Object.new) # => Schemacop::Exceptions::ValidationError: /: Invalid type, expected "String".
900
+ schema.validate!('foo') # => "foo"
901
+ schema.validate!('foo'.html_safe) # => "foo"
902
+ ```
903
+
904
+ If you set the `strict` option to `false`, the check is done using `is_a?` instead of
905
+ `instance_of?`, which also allows subclasses
906
+
907
+ ### AllOf
908
+
909
+ Type: `:all_of`\
910
+ DSL: `all_of`
911
+
912
+ With the AllOf node you can specify multiple schemas, for which the given value
913
+ needs to validate against every one:
914
+
915
+ ```ruby
916
+ schema = Schemacop::Schema3.new :all_of do
917
+ str min_length: 2
918
+ str max_length: 4
919
+ end
920
+
921
+ schema.validate!('foo') # => "foo"
922
+ schema.validate!('foooo') # => Schemacop::Exceptions::ValidationError: /: Does not match all allOf conditions.
923
+ ```
924
+
925
+ Please note that it's possible to create nonsensical schemas with this node, as
926
+ you can combine multiple schemas which contradict each other:
927
+
928
+ ```ruby
929
+ schema = Schemacop::Schema3.new :all_of do
930
+ str min_length: 4
931
+ str max_length: 1
932
+ end
933
+
934
+ schema.validate!('foo') # => Schemacop::Exceptions::ValidationError: /: Does not match all allOf conditions.
935
+ schema.validate!('foooo') # => Schemacop::Exceptions::ValidationError: /: Does not match all allOf conditions.
936
+ ```
937
+
938
+ ### AnyOf
939
+
940
+ Type: `:any_of`\
941
+ DSL: `any_of`
942
+
943
+ Similar to the `all_of` node, you can specify multiple schemas, for which the
944
+ given value needs to validate against at least one of the schemas.
945
+
946
+ For example, your value needs to be either a string which is at least 2
947
+ characters long, or an integer:
948
+
949
+ ```ruby
950
+ schema = Schemacop::Schema3.new :any_of do
951
+ str min_length: 2
952
+ int
953
+ end
954
+
955
+ schema.validate!('f') # => Schemacop::Exceptions::ValidationError: /: Does not match any anyOf condition.
956
+ schema.validate!('foo') # => "foo"
957
+ schema.validate!(42) # => 42
958
+ ```
959
+
960
+ Please note that you need to specify at least one item in the `any_of` node:
961
+
962
+ ```ruby
963
+ Schemacop::Schema3.new :any_of # => Schemacop::Exceptions::InvalidSchemaError: Node "any_of" makes only sense with at least 1 item.
964
+ ```
965
+
966
+ ### OneOf
967
+
968
+ Type: `:one_of`\
969
+ DSL: `one_of`
970
+
971
+ Similar to the `all_of` node, you can specify multiple schemas, for which the
972
+ given value needs to validate against exaclty one of the schemas. If the given
973
+ value validates against multiple schemas, the value is invalid.
974
+
975
+ For example, if you want an integer which is either a multiple of 2 or 3,
976
+ but not both (i.e. no multiple of 6), you could do it as follows:
977
+
978
+ ```ruby
979
+ schema = Schemacop::Schema3.new :one_of do
980
+ int multiple_of: 2
981
+ int multiple_of: 3
982
+ end
983
+
984
+ schema.validate!(2) # => 2
985
+ schema.validate!(3) # => 3
986
+ schema.validate!(4) # => 4
987
+ schema.validate!(5) # => Schemacop::Exceptions::ValidationError: /: Matches 0 definitions but should match exactly 1.
988
+ schema.validate!(6) # => Schemacop::Exceptions::ValidationError: /: Matches 2 definitions but should match exactly 1.
989
+ ```
990
+
991
+ Again, as previously with the AllOf node, you're allowed to create schemas
992
+ which will not work for any input, e.g. by specifying the same schema twice:
993
+
994
+ ```ruby
995
+ schema = Schemacop::Schema3.new :one_of do
996
+ int multiple_of: 2
997
+ int multiple_of: 2
998
+ end
999
+
1000
+ schema.validate!(2) # => Schemacop::Exceptions::ValidationError: /: Matches 2 definitions but should match exactly 1.
1001
+ schema.validate!(3) # => Schemacop::Exceptions::ValidationError: /: Matches 0 definitions but should match exactly 1.
1002
+ schema.validate!(4) # => Schemacop::Exceptions::ValidationError: /: Matches 2 definitions but should match exactly 1.
1003
+ schema.validate!(5) # => Schemacop::Exceptions::ValidationError: /: Matches 0 definitions but should match exactly 1.
1004
+ schema.validate!(6) # => Schemacop::Exceptions::ValidationError: /: Matches 2 definitions but should match exactly 1.
1005
+ ```
1006
+
1007
+ ### IsNot
1008
+
1009
+ Type: `:is_not`\
1010
+ DSL: `is_not`
1011
+
1012
+ With the `is_not` node, you can specify a schema which the given value must not
1013
+ validate against, i.e. every value which matches the schema will make this node
1014
+ invalid.
1015
+
1016
+ For example, you want anything but the numbers between 3 and 5:
1017
+
1018
+ ```ruby
1019
+ schema = Schemacop::Schema3.new :is_not do
1020
+ int minimum: 3, maximum: 5
1021
+ end
1022
+
1023
+ schema.validate!(nil) # => nil
1024
+ schema.validate!(1) # => 1
1025
+ schema.validate!(2) # => 2
1026
+ schema.validate!(3) # => Schemacop::Exceptions::ValidationError: /: Must not match schema: {"type"=>"integer", "minimum"=>3, "maximum"=>5}.
1027
+ schema.validate!('foo') # => "foo"
1028
+ ```
1029
+
1030
+ Note that a `is_not` node needs exactly one item:
1031
+
1032
+ ```ruby
1033
+ schema = Schemacop::Schema3.new :is_not # => Schemacop::Exceptions::InvalidSchemaError: Node "is_not" only allows exactly one item.
1034
+ ```
1035
+
1036
+ ### Reference
1037
+
1038
+ **Referencing**
1039
+ DSL: `ref`\
1040
+ Type: `reference`
1041
+
1042
+ **Definition**
1043
+ DSL: `scm`
1044
+
1045
+ Finally, with the *Reference* node, you can define schemas and then later reference
1046
+ them for usage, e.g. when you have a rather long schema which you need at multiple
1047
+ places.
1048
+
1049
+ #### Examples
1050
+
1051
+ For example, let's define an object with an schema called `Address`, which we'll
1052
+ reference multiple times:
1053
+
1054
+ ```ruby
1055
+ schema = Schemacop::Schema3.new :hash do
1056
+ scm :Address do
1057
+ str! :street
1058
+ str! :zip_code
1059
+ str! :location
1060
+ str! :country
1061
+ end
1062
+
1063
+ ref! :shipping_address, :Address
1064
+ ref! :billing_address, :Address
1065
+ end
1066
+
1067
+ schema.validate!({}) # => Schemacop::Exceptions::ValidationError: /shipping_address: Value must be given. /billing_address: Value must be given.
1068
+ schema.validate!({
1069
+ shipping_address: 'foo',
1070
+ billing_address: 42
1071
+ })
1072
+ # => Schemacop::Exceptions::ValidationError: /shipping_address: Invalid type, expected "object". /billing_address: Invalid type, expected "object".
1073
+
1074
+ schema.validate!({
1075
+ shipping_address: {
1076
+ street: 'Example Street 42',
1077
+ zip_code: '12345',
1078
+ location: 'London',
1079
+ country: 'United Kingdom'
1080
+ },
1081
+ billing_address: {
1082
+ street: 'Main St.',
1083
+ zip_code: '54321',
1084
+ location: 'Washington DC',
1085
+ country: 'USA'
1086
+ }
1087
+ })
1088
+
1089
+ # => {"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"}}
1090
+ ```
1091
+
1092
+ Note that if you use the reference node with the long type name `reference`,
1093
+ e.g. in an array, you need to specify the "name" of the schema in the
1094
+ `path` option:
1095
+
1096
+ ```ruby
1097
+ schema = Schemacop::Schema3.new :array do
1098
+ scm :User do
1099
+ str! :first_name
1100
+ str! :last_name
1101
+ end
1102
+
1103
+ list :reference, path: :User
1104
+ end
1105
+
1106
+ schema.validate!([]) # => []
1107
+ schema.validate!([{first_name: 'Joe', last_name: 'Doe'}]) # => [{"first_name"=>"Joe", "last_name"=>"Doe"}]
1108
+ schema.validate!([id: 42, first_name: 'Joe']) # => Schemacop::Exceptions::ValidationError: /[0]/last_name: Value must be given. /[0]: Obsolete property "id".
1109
+ ```
1110
+
1111
+ ## Context
1112
+
1113
+ Schemacop also features the concept of a `Context`. You can define schemas in a
1114
+ context, and then reference them in other schemas in that context. This is e.g.
1115
+ useful if you need a part of the schema to be different depending on the
1116
+ business action.
1117
+
1118
+ Examples:
1119
+
1120
+ ```ruby
1121
+ # Define a new context
1122
+ context = Schemacop::V3::Context.new
1123
+
1124
+ # Define the :Person schema in that context
1125
+ context.schema :Person do
1126
+ str! :first_name
1127
+ str! :last_name
1128
+ ref? :info, :PersonInfo
1129
+ end
1130
+
1131
+ # And also define the :PersonInfo schema in that context
1132
+ context.schema :PersonInfo do
1133
+ str! :born_at, format: :date
1134
+ end
1135
+
1136
+ # Now we can define our general schema, where we reference the :Person schema.
1137
+ # Note that at this point, we don't know what's in the :Person schema.
1138
+ schema = Schemacop::Schema3.new :reference, path: :Person
1139
+
1140
+ # Validate the data in the context we defined before, where we need the first_name
1141
+ # and last_name of a person, as well as an optional info hash with the born_at date
1142
+ # of the person.
1143
+ Schemacop.with_context context do
1144
+ schema.validate!({first_name: 'Joe', last_name: 'Doe', info: { born_at: '1980-01-01'} })
1145
+ # => {"first_name"=>"Joe", "last_name"=>"Doe", "info"=>{"born_at"=>Tue, 01 Jan 1980}}
1146
+ end
1147
+
1148
+ # Now we might want another context, where the person is more anonymous, and as
1149
+ # such, we need another schema
1150
+ other_context = Schemacop::V3::Context.new
1151
+
1152
+ # Here, we only want the nickname of the person
1153
+ other_context.schema :Person do
1154
+ str! :nickname
1155
+ end
1156
+
1157
+ # Finally, validate the data in the new context. We do not want the real name or
1158
+ # birth date of the person, instead only the nickname is allowed.
1159
+ Schemacop.with_context other_context do
1160
+ schema.validate!({first_name: 'Joe', last_name: 'Doe', info: { born_at: '1980-01-01'} })
1161
+ # => Schemacop::Exceptions::ValidationError: /nickname: Value must be given.
1162
+ # /: Obsolete property "first_name".
1163
+ # /: Obsolete property "last_name".
1164
+ # /: Obsolete property "info".
1165
+
1166
+ schema.validate!({nickname: 'J.'}) # => {"nickname"=>"J."}
1167
+ end
1168
+ ```
1169
+
1170
+ As one can see, we validated the data against the same schema, but because we
1171
+ defined the referenced schemas differently in the two contexts, we were able
1172
+ to use other data in the second context than in the first.
1173
+
1174
+ ## External schemas
1175
+
1176
+ Finally, Schemacop features the possibility to specify schemas in seperate
1177
+ files. This is especially useful is you have schemas in your application which
1178
+ are used multiple times throughout the application.
1179
+
1180
+ For each schema, you define the schema in a separate file, and after loading the
1181
+ schemas, you can reference them in other schemas.
1182
+
1183
+ The default load path is `'app/schemas'`, but this can be configured by setting
1184
+ the value of the `load_paths` attribute of the `Schemacop` module.
1185
+
1186
+ Please note that the following predescence order is used for the schemas:
1187
+
1188
+ ```
1189
+ local schemas > context schemas > global schemas
1190
+ ```
1191
+
1192
+ Where:
1193
+
1194
+ * local schemas: Defined by using the DSL method? `scm`
1195
+ * context schemas: Defined in the current context using `context.schema`
1196
+ * global schemas: Defined in a ruby file in the load path
1197
+
1198
+ ### Rails applications
1199
+
1200
+ In Rails applications, your schemas are automatically eager-laoded from the load
1201
+ path `'app/schemas'` when your application is started.
1202
+
1203
+ After starting your application, you can reference them like normally defined
1204
+ reference schemas, with the name being relative to the load path.
1205
+
1206
+ Example:
1207
+
1208
+ You defined the following two schemas in the `'app/schemas'` directory:
1209
+
1210
+ ```ruby
1211
+ # app/schemas/user.rb
1212
+ schema :hash do
1213
+ str! :first_name
1214
+ str! :last_name
1215
+ ary? :groups do
1216
+ list :reference, path: 'nested/group'
1217
+ end
1218
+ end
1219
+ ```
1220
+
1221
+ ```ruby
1222
+ # app/schemas/nested/user.rb
1223
+ schema :hash do
1224
+ str! :name
1225
+ end
1226
+ ```
1227
+
1228
+ To use the schema, you then can simply reference the schema as with normal
1229
+ reference schemas:
1230
+
1231
+ ```ruby
1232
+ schema = Schemacop::Schema3.new :hash do
1233
+ ref! :usr, :user
1234
+ end
1235
+
1236
+ schema.validate!({usr: {first_name: 'Joe', last_name: 'Doe'}})
1237
+ # => {"usr"=>{"first_name"=>"Joe", "last_name"=>"Doe"}}
1238
+
1239
+ schema.validate!({usr: {first_name: 'Joe', last_name: 'Doe', groups: []}})
1240
+ # => {"usr"=>{"first_name"=>"Joe", "last_name"=>"Doe", "groups"=>[]}}
1241
+
1242
+ schema.validate!({usr: {first_name: 'Joe', last_name: 'Doe', groups: [{name: 'foo'}, {name: 'bar'}]}})
1243
+ # => {"usr"=>{"first_name"=>"Joe", "last_name"=>"Doe", "groups"=>[{"name"=>"foo"}, {"name"=>"bar"}]}}
1244
+ ```
1245
+
1246
+ ### Non-Rails applications
1247
+
1248
+ Usage in non-Rails applications is the same as with usage in Rails applications,
1249
+ however you need to eager load the schemas yourself:
1250
+
1251
+ ```ruby
1252
+ Schemacop::V3::GlobalContext.eager_load!
1253
+ ```