schemacop 2.4.5 → 3.0.0.rc2

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