schemacop 2.4.7 → 3.0.0.rc4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (173) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +3 -0
  3. data/.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
+ ```