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
@@ -0,0 +1,170 @@
1
+ require 'test_helper'
2
+
3
+ module Schemacop
4
+ module V3
5
+ class ObjectNodeTest < V3Test
6
+ def test_basic
7
+ schema :object
8
+
9
+ assert_validation nil
10
+ assert_validation true
11
+ assert_validation false
12
+ assert_validation Object.new
13
+ assert_validation 'foo'
14
+
15
+ assert_json({})
16
+ end
17
+
18
+ def test_required_with_no_types
19
+ schema :object, required: true
20
+
21
+ assert_validation nil do
22
+ error '/', 'Value must be given.'
23
+ end
24
+ end
25
+
26
+ def test_with_classes
27
+ schema :object, classes: [String, Date]
28
+ assert_validation 'foo'
29
+ assert_validation Date.today
30
+ assert_validation({}.with_indifferent_access) do
31
+ error '/', 'Invalid type, expected "Date" or "String".'
32
+ end
33
+ assert_validation DateTime.now do
34
+ error '/', 'Invalid type, expected "Date" or "String".'
35
+ end
36
+ end
37
+
38
+ def test_non_strict
39
+ schema :object, classes: [String, Date, Hash], strict: false
40
+ assert_validation 'foo'
41
+ assert_validation 'foo'.html_safe
42
+ assert_validation Date.today
43
+ assert_validation nil
44
+ assert_validation DateTime.now
45
+ assert_validation({}.with_indifferent_access)
46
+ assert_validation Time.now do
47
+ error '/', 'Invalid type, expected "Date" or "Hash" or "String".'
48
+ end
49
+ end
50
+
51
+ def test_required
52
+ schema :object, required: true
53
+
54
+ assert_validation true
55
+ assert_validation false
56
+ assert_validation nil do
57
+ error '/', 'Value must be given.'
58
+ end
59
+ end
60
+
61
+ def test_hash
62
+ schema { obj! :myobj, String }
63
+ assert_json(
64
+ type: :object,
65
+ properties: {
66
+ myobj: {}
67
+ },
68
+ required: %i[myobj],
69
+ additionalProperties: false
70
+ )
71
+ assert_validation myobj: ''
72
+ assert_validation myobj: '42'
73
+ assert_validation myobj: Date.today do
74
+ error '/myobj', 'Invalid type, expected "String".'
75
+ end
76
+ assert_validation({}) do
77
+ error '/myobj', 'Value must be given.'
78
+ end
79
+ end
80
+
81
+ def test_enum_schema
82
+ schema :object, enum: [true, 'foo', :baz, [], { qux: '123' }]
83
+
84
+ # Can't represent a Ruby Object as a JSON value
85
+ assert_json({})
86
+
87
+ # As we didn't provide any classes, any object (i.e. everything) will
88
+ # be validated. However, only those elements we put into the enum list
89
+ # will be allowed
90
+ assert_validation(nil)
91
+ assert_validation(true)
92
+ assert_validation('foo')
93
+ assert_validation(:baz)
94
+ assert_validation([])
95
+ assert_validation({ qux: '123' })
96
+
97
+ # These will fail, as we didn't put them into the enum list
98
+ assert_validation(1) do
99
+ error '/', 'Value not included in enum [true, "foo", :baz, [], {:qux=>"123"}].'
100
+ end
101
+ assert_validation(:bar) do
102
+ error '/', 'Value not included in enum [true, "foo", :baz, [], {:qux=>"123"}].'
103
+ end
104
+ assert_validation({ qux: 42 }) do
105
+ error '/', 'Value not included in enum [true, "foo", :baz, [], {:qux=>"123"}].'
106
+ end
107
+ end
108
+
109
+ def test_enum_schema_with_classes
110
+ schema :object, classes: [String, Symbol, TrueClass], enum: [true, 'foo', :baz, [], { qux: '123' }, false]
111
+
112
+ # Can't represent a Ruby Object as a JSON value
113
+ assert_json({})
114
+
115
+ # Values need to be one of the classed we defined above, as well as in the
116
+ # enum list for the validation to pass
117
+ assert_validation(nil)
118
+ assert_validation(true)
119
+ assert_validation('foo')
120
+ assert_validation(:baz)
121
+
122
+ # These will fail, as they aren't of one of the classed we defined above
123
+ assert_validation([]) do
124
+ error '/', 'Invalid type, expected "String" or "Symbol" or "TrueClass".'
125
+ end
126
+ assert_validation({ qux: '123' }) do
127
+ error '/', 'Invalid type, expected "String" or "Symbol" or "TrueClass".'
128
+ end
129
+ assert_validation(false) do
130
+ error '/', 'Invalid type, expected "String" or "Symbol" or "TrueClass".'
131
+ end
132
+ end
133
+
134
+ def test_with_generic_keywords
135
+ schema :object, enum: [1, 'foo'],
136
+ title: 'Object schema',
137
+ description: 'Object schema holding generic keywords',
138
+ examples: [
139
+ 'foo'
140
+ ]
141
+
142
+ assert_json({})
143
+ end
144
+
145
+ def test_validate_self
146
+ assert_raises_with_message Exceptions::InvalidSchemaError,
147
+ 'Option "strict" must be a "boolean".' do
148
+ schema :object, strict: 'false'
149
+ end
150
+
151
+ assert_raises_with_message Exceptions::InvalidSchemaError,
152
+ 'Option "strict" must be a "boolean".' do
153
+ schema :object, strict: 123
154
+ end
155
+
156
+ assert_raises_with_message Exceptions::InvalidSchemaError,
157
+ 'Option "strict" must be a "boolean".' do
158
+ schema :object, strict: [1, 2, 3]
159
+ end
160
+
161
+ # rubocop:disable Lint/BooleanSymbol
162
+ assert_raises_with_message Exceptions::InvalidSchemaError,
163
+ 'Option "strict" must be a "boolean".' do
164
+ schema :object, strict: :false
165
+ end
166
+ # rubocop:enable Lint/BooleanSymbol
167
+ end
168
+ end
169
+ end
170
+ end
@@ -0,0 +1,187 @@
1
+ require 'test_helper'
2
+
3
+ module Schemacop
4
+ module V3
5
+ class OneOfNodeTest < V3Test
6
+ def test_optional
7
+ schema :one_of do
8
+ num multiple_of: 2
9
+ num multiple_of: 3
10
+ str
11
+ end
12
+
13
+ assert_validation(nil)
14
+ assert_validation(4)
15
+ assert_validation(9)
16
+ assert_validation('foo')
17
+ assert_validation(12) do
18
+ error '/', 'Matches 2 definitions but should match exactly 1.'
19
+ end
20
+ assert_validation(1) do
21
+ error '/', 'Matches 0 definitions but should match exactly 1.'
22
+ end
23
+ assert_validation(:foo) do
24
+ error '/', 'Matches 0 definitions but should match exactly 1.'
25
+ end
26
+ end
27
+
28
+ def test_required
29
+ schema :one_of, required: true do
30
+ num multiple_of: 2
31
+ num multiple_of: 3
32
+ str
33
+ end
34
+
35
+ assert_validation(8)
36
+ assert_validation(9)
37
+
38
+ assert_validation(nil) do
39
+ error '/', 'Value must be given.'
40
+ end
41
+ end
42
+
43
+ def test_nested
44
+ schema :one_of do
45
+ hsh do
46
+ one_of! :foo do
47
+ num multiple_of: 2
48
+ num multiple_of: 3
49
+ end
50
+ end
51
+ hsh do
52
+ num? :foo, multiple_of: 7
53
+ end
54
+ end
55
+
56
+ assert_validation(foo: 2)
57
+ assert_validation(foo: 9)
58
+ assert_validation(foo: 7)
59
+ assert_validation(foo: 14) do
60
+ error '/', 'Matches 2 definitions but should match exactly 1.'
61
+ end
62
+ assert_validation(foo: 12) do
63
+ error '/', 'Matches 0 definitions but should match exactly 1.'
64
+ end
65
+
66
+ assert_json(
67
+ oneOf: [
68
+ {
69
+ type: :object,
70
+ properties: {
71
+ foo: {
72
+ oneOf: [
73
+ { type: :number, multipleOf: 2 },
74
+ { type: :number, multipleOf: 3 }
75
+ ]
76
+ }
77
+ },
78
+ required: %i[foo],
79
+ additionalProperties: false
80
+ },
81
+ {
82
+ type: :object,
83
+ properties: {
84
+ foo: { type: :number, multipleOf: 7 }
85
+ },
86
+ additionalProperties: false
87
+ }
88
+ ]
89
+ )
90
+ end
91
+
92
+ def test_casting
93
+ schema do
94
+ one_of! :created_at do
95
+ str format: :date
96
+ str format: :date_time
97
+ end
98
+ end
99
+
100
+ assert_validation(created_at: '2020-01-01')
101
+ assert_validation(created_at: '2020-01-01T17:38:20')
102
+
103
+ assert_cast(
104
+ { created_at: '2020-01-01' },
105
+ { created_at: Date.new(2020, 1, 1) }.with_indifferent_access
106
+ )
107
+ assert_cast(
108
+ { created_at: '2020-01-01T17:38:20' },
109
+ { created_at: DateTime.new(2020, 1, 1, 17, 38, 20) }.with_indifferent_access
110
+ )
111
+ end
112
+
113
+ def test_defaults
114
+ schema do
115
+ one_of! :foo do
116
+ hsh { str? :bar }
117
+ hsh { str? :baz, default: 'Baz' }
118
+ end
119
+ end
120
+
121
+ assert_validation(foo: { bar: 'Bar' })
122
+ assert_validation(foo: { baz: 'Baz' })
123
+
124
+ assert_validation(foo: { xyz: 'Baz' }) do
125
+ error '/foo', 'Matches 0 definitions but should match exactly 1.'
126
+ end
127
+
128
+ assert_cast(
129
+ { foo: { bar: nil } },
130
+ { foo: { bar: nil } }.with_indifferent_access
131
+ )
132
+
133
+ assert_cast(
134
+ { foo: { baz: nil } },
135
+ { foo: { baz: 'Baz' } }.with_indifferent_access
136
+ )
137
+
138
+ schema do
139
+ one_of! :foo do
140
+ hsh { str? :bar, format: :date }
141
+ hsh { str? :bar, default: 'Baz', format: :date_time }
142
+ end
143
+ end
144
+
145
+ assert_cast(
146
+ { foo: { bar: '1990-01-13' } },
147
+ { foo: { bar: Date.new(1990, 1, 13) } }.with_indifferent_access
148
+ )
149
+
150
+ assert_cast(
151
+ { foo: { bar: '1990-01-13T10:00:00Z' } },
152
+ { foo: { bar: DateTime.new(1990, 1, 13, 10, 0, 0) } }.with_indifferent_access
153
+ )
154
+ end
155
+
156
+ def test_with_generic_keywords
157
+ schema :one_of, title: 'oneOf schema',
158
+ description: 'oneOf schema holding generic keywords',
159
+ examples: [
160
+ 'foo'
161
+ ] do
162
+ str
163
+ int
164
+ end
165
+
166
+ assert_json({
167
+ oneOf: [
168
+ { type: :string },
169
+ { type: :integer }
170
+ ],
171
+ title: 'oneOf schema',
172
+ description: 'oneOf schema holding generic keywords',
173
+ examples: [
174
+ 'foo'
175
+ ]
176
+ })
177
+ end
178
+
179
+ def test_invalid_schema
180
+ assert_raises_with_message Exceptions::InvalidSchemaError,
181
+ 'Node "one_of" makes only sense with at least 2 items.' do
182
+ schema :one_of
183
+ end
184
+ end
185
+ end
186
+ end
187
+ end
@@ -0,0 +1,367 @@
1
+ require 'test_helper'
2
+
3
+ module Schemacop
4
+ module V3
5
+ class ReferenceNodeTest < V3Test
6
+ def test_in_hash
7
+ context = Context.new
8
+ context.schema :MyString, :string
9
+ context.schema :MyInteger, :integer
10
+
11
+ Schemacop.with_context context do
12
+ schema do
13
+ ref? :foo, :MyString
14
+ ref? :int, :MyInteger
15
+ hsh? :bar do
16
+ ref? :foo, :MyString
17
+ hsh? :baz do
18
+ ref! :foo, :MyInteger
19
+ end
20
+ end
21
+ end
22
+
23
+ assert_validation({})
24
+ assert_validation(foo: 'String')
25
+ assert_validation(bar: { foo: 'String' })
26
+ assert_validation(bar: { foo: 'String', baz: { foo: 42 } })
27
+
28
+ assert_validation(foo: 42) do
29
+ error '/foo', 'Invalid type, expected "string".'
30
+ end
31
+ assert_validation(bar: { foo: 42 }) do
32
+ error '/bar/foo', 'Invalid type, expected "string".'
33
+ end
34
+ assert_validation(bar: { foo: 'String', baz: { foo: '42' } }) do
35
+ error '/bar/baz/foo', 'Invalid type, expected "integer".'
36
+ end
37
+
38
+ assert_json({
39
+ properties: {
40
+ foo: {
41
+ '$ref' => '#/definitions/MyString'
42
+ },
43
+ int: {
44
+ '$ref' => '#/definitions/MyInteger'
45
+ },
46
+ bar: {
47
+ properties: {
48
+ foo: {
49
+ '$ref' => '#/definitions/MyString'
50
+ },
51
+ baz: {
52
+ properties: {
53
+ foo: {
54
+ '$ref' => '#/definitions/MyInteger'
55
+ }
56
+ },
57
+ additionalProperties: false,
58
+ required: ['foo'],
59
+ type: :object
60
+ }
61
+ },
62
+ additionalProperties: false,
63
+ type: :object
64
+ }
65
+ },
66
+ additionalProperties: false,
67
+ type: :object
68
+ })
69
+ end
70
+ end
71
+
72
+ def test_schema_not_found
73
+ assert_raises_with_message RuntimeError, 'Schema "MyInteger" not found.' do
74
+ schema do
75
+ ref? :int, :MyInteger
76
+ end
77
+ assert_validation(int: 5)
78
+ end
79
+ end
80
+
81
+ def test_multiple_schemas
82
+ schema do
83
+ scm :Address do
84
+ str! :street
85
+ str! :zip_code
86
+ str! :location
87
+ str! :country
88
+ end
89
+
90
+ scm :Person do
91
+ str! :first_name
92
+ str! :last_name
93
+ str! :birthday, format: :date
94
+ end
95
+
96
+ ref! :person_info, :Person
97
+ ref! :shipping_address, :Address
98
+ ref! :billing_address, :Address
99
+ end
100
+
101
+ assert_json({
102
+ definitions: {
103
+ Address: {
104
+ properties: {
105
+ street: {
106
+ type: :string
107
+ },
108
+ zip_code: {
109
+ type: :string
110
+ },
111
+ location: {
112
+ type: :string
113
+ },
114
+ country: {
115
+ type: :string
116
+ }
117
+ },
118
+ additionalProperties: false,
119
+ required: %w[street zip_code location country],
120
+ type: :object
121
+ },
122
+ Person: {
123
+ properties: {
124
+ first_name: {
125
+ type: :string
126
+ },
127
+ last_name: {
128
+ type: :string
129
+ },
130
+ birthday: {
131
+ type: :string,
132
+ format: :date
133
+ }
134
+ },
135
+ additionalProperties: false,
136
+ required: %w[first_name last_name birthday],
137
+ type: :object
138
+ }
139
+ },
140
+ properties: {
141
+ person_info: {
142
+ '$ref' => '#/definitions/Person'
143
+ },
144
+ shipping_address: {
145
+ '$ref' => '#/definitions/Address'
146
+ },
147
+ billing_address: {
148
+ '$ref' => '#/definitions/Address'
149
+ }
150
+ },
151
+ type: :object,
152
+ additionalProperties: false,
153
+ required: %w[
154
+ person_info
155
+ shipping_address
156
+ billing_address
157
+ ]
158
+ })
159
+
160
+ assert_validation(nil)
161
+ assert_validation({
162
+ person_info: {
163
+ first_name: 'Joe',
164
+ last_name: 'Doe',
165
+ birthday: '1990-01-01'
166
+ },
167
+ billing_address: {
168
+ street: 'Badenerstrasse 530',
169
+ zip_code: '8048',
170
+ location: 'Zürich',
171
+ country: 'Switzerland'
172
+ },
173
+ shipping_address: {
174
+ street: 'Badenerstrasse 530',
175
+ zip_code: '8048',
176
+ location: 'Zürich',
177
+ country: 'Switzerland'
178
+ }
179
+ })
180
+
181
+ assert_validation({}) do
182
+ error '/person_info', 'Value must be given.'
183
+ error '/shipping_address', 'Value must be given.'
184
+ error '/billing_address', 'Value must be given.'
185
+ end
186
+ end
187
+
188
+ def test_nested_schemas
189
+ schema do
190
+ scm :User do
191
+ str! :first_name
192
+ str! :last_name
193
+ ary? :groups do
194
+ list :reference, path: :Group
195
+ end
196
+ end
197
+
198
+ scm :Group do
199
+ str! :name
200
+ end
201
+ end
202
+
203
+ assert_json({
204
+ additionalProperties: false,
205
+ definitions: {
206
+ User: {
207
+ properties: {
208
+ first_name: {
209
+ type: :string
210
+ },
211
+ last_name: {
212
+ type: :string
213
+ },
214
+ groups: {
215
+ type: :array,
216
+ items: {
217
+ '$ref' => '#/definitions/Group'
218
+ }
219
+ }
220
+ },
221
+ additionalProperties: false,
222
+ required: %w[first_name last_name],
223
+ type: :object
224
+ },
225
+ Group: {
226
+ properties: {
227
+ name: {
228
+ type: :string
229
+ }
230
+ },
231
+ additionalProperties: false,
232
+ required: ['name'],
233
+ type: :object
234
+ }
235
+ },
236
+ type: :object
237
+ })
238
+ end
239
+
240
+ def test_in_hash_recursion
241
+ schema do
242
+ scm :Node do
243
+ str! :name
244
+ ary? :children, min_items: 1 do
245
+ list :reference, path: :Node
246
+ end
247
+ end
248
+
249
+ ref? :node, :Node
250
+ end
251
+
252
+ assert_equal(@schema.root.used_external_schemas, [])
253
+
254
+ assert_validation({})
255
+ assert_validation(node: { name: '1', children: [{ name: '1' }, { name: '2' }] })
256
+ assert_validation(
257
+ node: {
258
+ name: '1',
259
+ children: [
260
+ { name: '1.1' },
261
+ {
262
+ name: '1.2',
263
+ children: [
264
+ { name: '1.2.1' }
265
+ ]
266
+ }
267
+ ]
268
+ }
269
+ )
270
+
271
+ assert_validation(
272
+ node: {
273
+ name: '1',
274
+ children: [
275
+ { name: '1.1' },
276
+ {
277
+ name: '1.2',
278
+ children: [
279
+ { name: '1.2.1', children: [] }
280
+ ]
281
+ },
282
+ { name: '1.3', foo: :bar }
283
+ ]
284
+ }
285
+ ) do
286
+ error '/node/children/[1]/children/[0]/children', 'Array has 0 items but needs at least 1.'
287
+ error '/node/children/[2]', 'Obsolete property "foo".'
288
+ end
289
+ end
290
+
291
+ def test_external_schemas
292
+ context = Context.new
293
+
294
+ context.schema :Person do
295
+ str! :first_name
296
+ str! :last_name
297
+ ref? :info, :PersonInfo
298
+ end
299
+
300
+ context.schema :PersonInfo do
301
+ str! :born_at, format: :date
302
+ end
303
+
304
+ schema :reference, path: :Person
305
+
306
+ with_context context do
307
+ assert_validation(first_name: 'John', last_name: 'Doe')
308
+ assert_validation(first_name: 'John', last_name: 42) do
309
+ error '/last_name', 'Invalid type, expected "string".'
310
+ end
311
+ end
312
+
313
+ with_context context do
314
+ schema do
315
+ ref! :person, :Person
316
+ end
317
+
318
+ assert_equal(@schema.root.used_external_schemas, %i[Person PersonInfo])
319
+
320
+ assert_validation(person: { first_name: 'John', last_name: 'Doe' })
321
+ assert_validation(person: { first_name: 'John', last_name: 'Doe', info: { born_at: '1990-01-13' } })
322
+ assert_validation(person: { first_name_x: 'John', last_name: 'Doe' }) do
323
+ error '/person', 'Obsolete property "first_name_x".'
324
+ error '/person/first_name', 'Value must be given.'
325
+ end
326
+ assert_validation(person: { first_name: 'John', last_name: 'Doe', info: { born_at: 'never' } }) do
327
+ error '/person/info/born_at', 'String does not match format "date".'
328
+ end
329
+ end
330
+
331
+ with_context context do
332
+ schema do
333
+ scm :PersonNode do
334
+ ref! :person, :Person
335
+ end
336
+
337
+ ref! :personNode, :PersonNode
338
+ end
339
+
340
+ assert_equal(@schema.root.used_external_schemas, %i[Person PersonInfo])
341
+ end
342
+ end
343
+
344
+ def test_defaults
345
+ schema do
346
+ scm :Person do
347
+ str? :foo, default: 'bar'
348
+ end
349
+ ref? :person, :Person, default: {}
350
+ end
351
+
352
+ assert_cast({}, { person: { foo: 'bar' } }.with_indifferent_access)
353
+ end
354
+
355
+ def test_casting
356
+ schema do
357
+ scm :Person do
358
+ str! :born_at, format: :date
359
+ end
360
+ ref? :person, :Person, default: {}
361
+ end
362
+
363
+ assert_cast({ person: { born_at: '1990-01-13' } }, { person: { born_at: Date.new(1990, 1, 13) } }.with_indifferent_access)
364
+ end
365
+ end
366
+ end
367
+ end