schemacop 2.4.4 → 3.0.0.rc1

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