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,126 @@
1
+ require 'test_helper'
2
+ # rubocop:disable Lint/BooleanSymbol
3
+
4
+ module Schemacop
5
+ module V3
6
+ class BooleanNodeTest < V3Test
7
+ EXP_INVALID_TYPE = 'Invalid type, expected "boolean".'.freeze
8
+
9
+ def test_basic
10
+ schema :boolean
11
+
12
+ assert_validation true
13
+ assert_validation false
14
+
15
+ assert_json(type: :boolean)
16
+ end
17
+
18
+ def test_required
19
+ schema :boolean, required: true
20
+
21
+ assert_validation true
22
+ assert_validation false
23
+ assert_validation nil do
24
+ error '/', 'Value must be given.'
25
+ end
26
+
27
+ assert_json(type: :boolean)
28
+ end
29
+
30
+ def test_hash
31
+ schema { boo! :alive }
32
+ assert_json(
33
+ type: :object,
34
+ properties: {
35
+ alive: { type: :boolean }
36
+ },
37
+ required: %i[alive],
38
+ additionalProperties: false
39
+ )
40
+ assert_validation alive: true
41
+ assert_validation alive: false
42
+ end
43
+
44
+ def test_type
45
+ schema :boolean
46
+
47
+ assert_json(type: :boolean)
48
+
49
+ assert_validation 42 do
50
+ error '/', EXP_INVALID_TYPE
51
+ end
52
+
53
+ [:true, 'true', :false, 'false', 0, 1].each do |value|
54
+ assert_validation value do
55
+ error '/', EXP_INVALID_TYPE
56
+ end
57
+ end
58
+
59
+ schema { boo? :name }
60
+
61
+ assert_json(
62
+ type: :object,
63
+ properties: {
64
+ name: { type: :boolean }
65
+ },
66
+ additionalProperties: false
67
+ )
68
+
69
+ [:true, 'true', :false, 'false', 0, 1].each do |value|
70
+ assert_validation name: value do
71
+ error '/name', EXP_INVALID_TYPE
72
+ end
73
+ end
74
+ end
75
+
76
+ def test_enum_schema
77
+ schema :boolean, enum: [1, 2, 'foo', :bar, { qux: 42 }, true]
78
+
79
+ assert_json({
80
+ type: :boolean,
81
+ enum: [1, 2, 'foo', :bar, { qux: 42 }, true]
82
+ })
83
+
84
+ assert_validation(nil)
85
+ assert_validation(true)
86
+
87
+ # Even we put those types in the enum, they need to fail the validations,
88
+ # as they are not booleans
89
+ assert_validation('foo') do
90
+ error '/', 'Invalid type, expected "boolean".'
91
+ end
92
+ assert_validation(:bar) do
93
+ error '/', 'Invalid type, expected "boolean".'
94
+ end
95
+ assert_validation({ qux: 42 }) do
96
+ error '/', 'Invalid type, expected "boolean".'
97
+ end
98
+
99
+ # These need to fail validation, as they are not in the enum list
100
+ assert_validation(false) do
101
+ error '/', 'Value not included in enum [1, 2, "foo", :bar, {:qux=>42}, true].'
102
+ end
103
+ end
104
+
105
+ def test_with_generic_keywords
106
+ schema :boolean, enum: [1, 'foo', true],
107
+ title: 'Boolean schema',
108
+ description: 'Boolean schema holding generic keywords',
109
+ examples: [
110
+ true
111
+ ]
112
+
113
+ assert_json({
114
+ type: :boolean,
115
+ enum: [1, 'foo', true],
116
+ title: 'Boolean schema',
117
+ description: 'Boolean schema holding generic keywords',
118
+ examples: [
119
+ true
120
+ ]
121
+ })
122
+ end
123
+ end
124
+ end
125
+ end
126
+ # rubocop:enable Lint/BooleanSymbol
@@ -0,0 +1,166 @@
1
+ require 'test_helper'
2
+
3
+ module Schemacop
4
+ module V3
5
+ class GlobalContextTest < V3Test
6
+ def setup
7
+ super
8
+ GlobalContext.instance_variable_set(:@instance, GlobalContext.send(:new))
9
+ Schemacop.load_paths = ['test/schemas']
10
+ end
11
+
12
+ def test_instance
13
+ assert_equal GlobalContext.instance, GlobalContext.instance
14
+ end
15
+
16
+ def test_eager_load
17
+ refute GlobalContext.instance.instance_variable_get(:@eager_loaded)
18
+ GlobalContext.instance.eager_load!
19
+ assert GlobalContext.instance.instance_variable_get(:@eager_loaded)
20
+
21
+ assert_raises_with_message RuntimeError, /can't be eager loaded more than once/ do
22
+ GlobalContext.instance.eager_load!
23
+ end
24
+ end
25
+
26
+ def test_schemas
27
+ assert_equal({}, GlobalContext.instance.schemas)
28
+ assert_equal({}, GlobalContext.schemas)
29
+ GlobalContext.instance.eager_load!
30
+ assert_equal(%i[nested/group user], GlobalContext.instance.schemas.keys)
31
+ assert_equal(%i[nested/group user], GlobalContext.schemas.keys)
32
+ assert_is_a Node, GlobalContext.instance.schemas.values.first
33
+ end
34
+
35
+ def test_schema_for_w_eager_loading
36
+ GlobalContext.instance.eager_load!
37
+
38
+ 2.times do
39
+ assert_is_a HashNode, GlobalContext.instance.schema_for('user')
40
+ assert_equal(%i[nested/group user], GlobalContext.instance.schemas.keys)
41
+ assert_is_a HashNode, GlobalContext.instance.schema_for('user')
42
+ end
43
+ end
44
+
45
+ def test_schema_for_wo_eager_loading
46
+ assert_is_a HashNode, GlobalContext.instance.schema_for('user')
47
+ assert_equal(%i[user], GlobalContext.instance.schemas.keys)
48
+ assert_is_a HashNode, GlobalContext.instance.schema_for('user')
49
+ assert_is_a HashNode, GlobalContext.instance.schema_for('nested/group')
50
+ assert_equal(%i[user nested/group], GlobalContext.instance.schemas.keys)
51
+ end
52
+
53
+ def test_file_reload
54
+ dir = Dir.mktmpdir
55
+ Schemacop.load_paths << dir
56
+ IO.write(File.join(dir, 'foo.rb'), %(schema :string))
57
+ assert_is_a StringNode, GlobalContext.instance.schema_for('foo')
58
+ IO.write(File.join(dir, 'foo.rb'), %(schema :integer))
59
+ assert_is_a IntegerNode, GlobalContext.instance.schema_for('foo')
60
+ end
61
+
62
+ def test_file_not_reloaded_in_eager
63
+ dir = Dir.mktmpdir
64
+ Schemacop.load_paths << dir
65
+
66
+ IO.write(File.join(dir, 'foo.rb'), %(schema :string))
67
+
68
+ GlobalContext.instance.eager_load!
69
+
70
+ assert_is_a StringNode, GlobalContext.instance.schema_for('foo')
71
+ IO.write(File.join(dir, 'foo.rb'), %(schema :integer))
72
+ assert_is_a StringNode, GlobalContext.instance.schema_for('foo')
73
+ end
74
+
75
+ def test_schema_not_found
76
+ assert_nil GlobalContext.instance.schema_for('foo')
77
+ GlobalContext.instance.eager_load!
78
+ assert_nil GlobalContext.instance.schema_for('foo')
79
+ end
80
+
81
+ def test_inter_references
82
+ schema = GlobalContext.instance.schema_for('user')
83
+
84
+ assert schema.validate(
85
+ first_name: 'John',
86
+ last_name: 'Doe',
87
+ groups: [
88
+ { name: 'Group 1' }
89
+ ]
90
+ ).valid?
91
+
92
+ refute schema.validate(
93
+ first_name: 'John',
94
+ last_name: 'Doe',
95
+ groups: [
96
+ { name_x: 'Group 1' }
97
+ ]
98
+ ).valid?
99
+
100
+ schema = GlobalContext.instance.schema_for('nested/group')
101
+
102
+ assert schema.validate(
103
+ name: 'Group 1',
104
+ users: [
105
+ { first_name: 'John', last_name: 'Doe' }
106
+ ]
107
+ ).valid?
108
+
109
+ refute schema.validate(
110
+ name: 'Group 1',
111
+ users: [
112
+ { first_name_x: 'John', last_name: 'Doe' }
113
+ ]
114
+ ).valid?
115
+ end
116
+
117
+ def test_empty_schema
118
+ dir = Dir.mktmpdir
119
+ Schemacop.load_paths << dir
120
+ IO.write(File.join(dir, 'foo.rb'), %())
121
+ assert_raises_with_message RuntimeError, /does not define any schema/ do
122
+ GlobalContext.instance.schema_for('foo')
123
+ end
124
+ end
125
+
126
+ def test_multiple_schemas
127
+ dir = Dir.mktmpdir
128
+ Schemacop.load_paths << dir
129
+ IO.write(File.join(dir, 'foo.rb'), %(schema :string\nschema :integer))
130
+ assert_raises_with_message RuntimeError, /Schema "#{File.join(dir, 'foo.rb')}" defines multiple schemas/ do
131
+ GlobalContext.instance.schema_for('foo')
132
+ end
133
+ end
134
+
135
+ def test_invalid_schema
136
+ dir = Dir.mktmpdir
137
+ Schemacop.load_paths << dir
138
+ IO.write(File.join(dir, 'foo.rb'), %(foobarbaz))
139
+
140
+ assert_raises_with_message RuntimeError, /Could not load schema/ do
141
+ GlobalContext.schema_for('foo')
142
+ end
143
+ end
144
+
145
+ def test_overrides_with_eager_load
146
+ dir = Dir.mktmpdir
147
+ Schemacop.load_paths << dir
148
+ IO.write(File.join(dir, 'user.rb'), %(schema :string))
149
+
150
+ assert_raises_with_message RuntimeError, %r{in both load paths "test/schemas" and "#{dir}"} do
151
+ GlobalContext.eager_load!
152
+ end
153
+ end
154
+
155
+ def test_overrides_with_lazy_load
156
+ dir = Dir.mktmpdir
157
+ Schemacop.load_paths << dir
158
+ IO.write(File.join(dir, 'user.rb'), %(schema :string))
159
+
160
+ assert_raises_with_message RuntimeError, %r{in both load paths "test/schemas" and "#{dir}"} do
161
+ GlobalContext.instance.schema_for('user')
162
+ end
163
+ end
164
+ end
165
+ end
166
+ end
@@ -0,0 +1,972 @@
1
+ require 'test_helper'
2
+
3
+ module Schemacop
4
+ module V3
5
+ class HashNodeTest < V3Test
6
+ EXP_INVALID_TYPE = 'Invalid type, expected "hash".'.freeze
7
+
8
+ def test_basic
9
+ schema
10
+ assert_validation({})
11
+ assert_json(type: :object, additionalProperties: false)
12
+
13
+ schema :hash
14
+ assert_validation({})
15
+
16
+ assert_json(type: :object, additionalProperties: false)
17
+ end
18
+
19
+ def test_additional_properties_false_by_default
20
+ schema
21
+ assert_validation({})
22
+ assert_validation(foo: :bar, bar: :baz) do
23
+ error '/', 'Obsolete property "foo".'
24
+ error '/', 'Obsolete property "bar".'
25
+ end
26
+ assert_json(type: :object, additionalProperties: false)
27
+ end
28
+
29
+ def test_additional_properties_false
30
+ schema :hash, additional_properties: false
31
+
32
+ assert_validation({})
33
+ assert_validation(foo: :bar, bar: :baz) do
34
+ error '/', 'Obsolete property "foo".'
35
+ error '/', 'Obsolete property "bar".'
36
+ end
37
+ assert_json(type: :object, additionalProperties: false)
38
+ end
39
+
40
+ def test_additional_properties_true
41
+ schema :hash, additional_properties: true
42
+ assert_validation({})
43
+ assert_validation(foo: :bar)
44
+ assert_validation(foo: { bar: :baz })
45
+
46
+ assert_json(type: :object, additionalProperties: true)
47
+ end
48
+
49
+ def test_additional_properties_schema
50
+ schema :hash do
51
+ str! :foo
52
+ add :string
53
+ end
54
+
55
+ assert_validation(foo: 'bar', baz: 'foo', answer: '42')
56
+ assert_validation(foo: 'bar', baz: 'foo', answer: 42) do
57
+ error '/answer', 'Invalid type, expected "string".'
58
+ end
59
+
60
+ assert_json(
61
+ properties: {
62
+ foo: { type: :string }
63
+ },
64
+ required: %i[foo],
65
+ type: :object,
66
+ additionalProperties: { type: :string }
67
+ )
68
+
69
+ assert_nothing_raised do
70
+ @schema.validate!({ foo: 'foo' })
71
+ end
72
+
73
+ assert_raises_with_message Exceptions::ValidationError, '/bar: Invalid type, expected "string".' do
74
+ @schema.validate!({ foo: 'foo', bar: :baz })
75
+ end
76
+ end
77
+
78
+ def test_property_names
79
+ schema :hash, additional_properties: true, property_names: '^[a-zA-Z0-9]+$'
80
+ assert_validation({})
81
+ assert_validation(foo: :bar)
82
+ assert_validation('foo' => 'bar')
83
+ assert_validation(Foo: :bar)
84
+ assert_validation('_foo39sjfdoi 345893(%' => 'bar', 'foo' => 'bar') do
85
+ error '/', 'Property name "_foo39sjfdoi 345893(%" does not match "^[a-zA-Z0-9]+$".'
86
+ end
87
+
88
+ assert_json(
89
+ type: :object,
90
+ additionalProperties: true,
91
+ propertyNames: '^[a-zA-Z0-9]+$'
92
+ )
93
+
94
+ assert_cast({ foo: 123 }, { foo: 123 }.with_indifferent_access)
95
+ assert_cast({ Foo: 123 }, { Foo: 123 }.with_indifferent_access)
96
+
97
+ # New schema
98
+ schema :hash, additional_properties: true, property_names: '^[a-z]+$'
99
+
100
+ assert_validation({})
101
+ assert_validation(foo: :bar)
102
+ assert_validation('foo' => 'bar')
103
+ assert_validation(Foo: :bar) do
104
+ error '/', 'Property name "Foo" does not match "^[a-z]+$".'
105
+ end
106
+ assert_validation('_foo39sjfdoi 345893(%' => 'bar', 'foo' => 'bar') do
107
+ error '/', 'Property name "_foo39sjfdoi 345893(%" does not match "^[a-z]+$".'
108
+ end
109
+
110
+ assert_cast({ foo: 123 }, { foo: 123 }.with_indifferent_access)
111
+ end
112
+
113
+ def test_required
114
+ schema do
115
+ str! :foo
116
+ int? :bar
117
+ end
118
+
119
+ assert_validation(foo: 'sdfsd')
120
+ assert_validation(foo: 'sdfsd', bar: 42)
121
+
122
+ assert_validation(bar: 42) do
123
+ error '/foo', 'Value must be given.'
124
+ end
125
+
126
+ assert_validation({}) do
127
+ error '/foo', 'Value must be given.'
128
+ end
129
+
130
+ assert_json(
131
+ type: :object,
132
+ properties: {
133
+ foo: { type: :string },
134
+ bar: { type: :integer }
135
+ },
136
+ required: %i[foo],
137
+ additionalProperties: false
138
+ )
139
+ end
140
+
141
+ def test_min_properties
142
+ schema :hash, min_properties: 2, additional_properties: true
143
+ assert_validation(foo: :bar, bar: :baz)
144
+ assert_validation(foo: :bar, bar: :baz, baz: :foo)
145
+
146
+ assert_validation(foo: :bar) do
147
+ error '/', 'Has 1 properties but needs at least 2.'
148
+ end
149
+
150
+ assert_validation({}) do
151
+ error '/', 'Has 0 properties but needs at least 2.'
152
+ end
153
+
154
+ assert_json(
155
+ type: :object,
156
+ minProperties: 2,
157
+ additionalProperties: true
158
+ )
159
+ end
160
+
161
+ def test_max_properties
162
+ schema :hash, max_properties: 3, additional_properties: true
163
+ assert_validation(foo: :bar, bar: :baz)
164
+ assert_validation(foo: :bar, bar: :baz, baz: :foo)
165
+
166
+ assert_validation(foo: :bar, bar: :baz, baz: :foo, answer: 42) do
167
+ error '/', 'Has 4 properties but needs at most 3.'
168
+ end
169
+
170
+ assert_json(
171
+ type: :object,
172
+ maxProperties: 3,
173
+ additionalProperties: true
174
+ )
175
+ end
176
+
177
+ def test_min_max_properties
178
+ schema :hash, min_properties: 3, max_properties: 3, additional_properties: true
179
+ assert_validation(foo: :bar, bar: :baz, baz: :foo)
180
+
181
+ assert_validation(foo: :bar, bar: :baz, baz: :foo, answer: 42) do
182
+ error '/', 'Has 4 properties but needs at most 3.'
183
+ end
184
+
185
+ assert_validation(foo: :bar, bar: :baz) do
186
+ error '/', 'Has 2 properties but needs at least 3.'
187
+ end
188
+
189
+ assert_json(
190
+ type: :object,
191
+ minProperties: 3,
192
+ maxProperties: 3,
193
+ additionalProperties: true
194
+ )
195
+ end
196
+
197
+ def test_dependencies
198
+ schema :hash do
199
+ str! :name
200
+ str? :credit_card
201
+ str? :billing_address
202
+ str? :phone_number
203
+
204
+ dep :credit_card, :billing_address, :phone_number
205
+ dep :billing_address, :credit_card
206
+ end
207
+
208
+ assert_validation(name: 'John')
209
+ assert_validation(name: 'John', credit_card: '23423523', billing_address: 'Example 3', phone_number: '234')
210
+
211
+ assert_validation(name: 'John', credit_card: '23423523') do
212
+ error '/', 'Missing property "billing_address" because "credit_card" is given.'
213
+ error '/', 'Missing property "phone_number" because "credit_card" is given.'
214
+ end
215
+
216
+ assert_validation(name: 'John', billing_address: 'Example 3') do
217
+ error '/', 'Missing property "credit_card" because "billing_address" is given.'
218
+ end
219
+
220
+ assert_json(
221
+ type: :object,
222
+ properties: {
223
+ name: { type: :string },
224
+ credit_card: { type: :string },
225
+ billing_address: { type: :string },
226
+ phone_number: { type: :string }
227
+ },
228
+ required: %i[name],
229
+ dependencies: {
230
+ credit_card: %i[billing_address phone_number],
231
+ billing_address: %i[credit_card]
232
+ },
233
+ additionalProperties: false
234
+ )
235
+ end
236
+
237
+ def test_pattern_properties_wo_additional
238
+ schema additional_properties: false do
239
+ str! :name
240
+ str?(/^foo_.*$/)
241
+ int?(/^bar_.*$/)
242
+ end
243
+
244
+ assert_validation(name: 'John', foo_bar: 'John')
245
+ assert_validation(name: 'John', foo_bar: 'John', bar_baz: 42, foo_baz: '42')
246
+ assert_validation(name: 'John', foo_baz: 'John', bar_baz: 42)
247
+
248
+ assert_validation(name: 'John', xy: 'John', bar_baz: 'Doe') do
249
+ error '/', 'Obsolete property "xy".'
250
+ error '/bar_baz', 'Invalid type, expected "integer".'
251
+ end
252
+
253
+ assert_json(
254
+ type: :object,
255
+ properties: {
256
+ name: { type: :string }
257
+ },
258
+ patternProperties: {
259
+ '^foo_.*$': { type: :string },
260
+ '^bar_.*$': { type: :integer }
261
+ },
262
+ additionalProperties: false,
263
+ required: %i[name]
264
+ )
265
+ end
266
+
267
+ def test_pattern_properties_w_additional
268
+ schema additional_properties: true do
269
+ int? :builtin
270
+ str?(/^S_/)
271
+ int?(/^I_/)
272
+ add :string
273
+ end
274
+
275
+ assert_validation(builtin: 42)
276
+ assert_validation(keyword: 'value')
277
+
278
+ assert_validation(keyword: 42) do
279
+ error '/keyword', 'Invalid type, expected "string".'
280
+ end
281
+
282
+ assert_json(
283
+ type: 'object',
284
+ properties: {
285
+ builtin: { type: :integer }
286
+ },
287
+ patternProperties: {
288
+ '^S_': { type: :string },
289
+ '^I_': { type: :integer }
290
+ },
291
+ additionalProperties: { type: :string }
292
+ )
293
+ end
294
+
295
+ def test_pattern_properties_casting
296
+ schema do
297
+ int?(/^id_.*$/)
298
+ int?(/^val.*$/)
299
+ end
300
+
301
+ assert_json({
302
+ type: :object,
303
+ patternProperties: {
304
+ '^id_.*$': { type: :integer },
305
+ '^val.*$': { type: :integer }
306
+ },
307
+ additionalProperties: false
308
+ })
309
+
310
+ assert_validation({})
311
+ assert_validation({ id_foo: 1 })
312
+ assert_validation({ id_foo: 1, id_bar: 2 })
313
+ assert_validation({ id_foo: 1, id_bar: 2, value: 4 })
314
+
315
+ assert_cast({ id_foo: 1 }, { id_foo: 1 }.with_indifferent_access)
316
+ assert_cast({ id_foo: 1, id_bar: 2 }, { id_foo: 1, id_bar: 2 }.with_indifferent_access)
317
+ assert_cast({ id_foo: 1, id_bar: 2, value: 4 }, { id_foo: 1, id_bar: 2, value: 4 }.with_indifferent_access)
318
+ end
319
+
320
+ def test_defaults
321
+ schema do
322
+ str? :first_name, default: 'John'
323
+ str? :last_name, default: 'Doe'
324
+ str! :active, format: :boolean
325
+ hsh? :address, default: {} do
326
+ str? :street, default: 'Example 42'
327
+ end
328
+ end
329
+
330
+ data = { last_name: 'Doeringer', active: 'true' }
331
+ data_was = data.dup
332
+
333
+ expected_data = { first_name: 'John', last_name: 'Doeringer', active: true, address: { street: 'Example 42' } }.with_indifferent_access
334
+
335
+ assert_equal(expected_data, @schema.validate(data).data)
336
+ assert_equal data_was, data
337
+
338
+ schema do
339
+ hsh? :address do
340
+ str? :street, default: 'Example 42'
341
+ end
342
+ end
343
+
344
+ assert_equal({}, @schema.validate({}).data)
345
+ end
346
+
347
+ def test_all_of
348
+ schema do
349
+ all_of! :str do
350
+ str min_length: 3
351
+ str max_length: 5
352
+ end
353
+ end
354
+
355
+ assert_validation(str: '123')
356
+ assert_validation(str: '1234')
357
+ assert_validation(str: '12345')
358
+ assert_validation(str: '0') do
359
+ error '/str', 'Does not match all allOf conditions.'
360
+ end
361
+ end
362
+
363
+ def test_one_of_required
364
+ schema do
365
+ one_of! :str do
366
+ str min_length: 4
367
+ str min_length: 0, max_length: 4
368
+ end
369
+ end
370
+
371
+ assert_validation(str: '12345')
372
+ assert_validation(str: '123')
373
+ assert_validation(str: nil) do
374
+ error '/str', 'Value must be given.'
375
+ end
376
+ assert_validation(str: '1234') do
377
+ error '/str', 'Matches 2 definitions but should match exactly 1.'
378
+ end
379
+ end
380
+
381
+ def test_one_of_optional
382
+ schema do
383
+ one_of? :str do
384
+ str min_length: 4
385
+ str min_length: 0, max_length: 4
386
+ end
387
+ end
388
+
389
+ assert_validation(str: '12345')
390
+ assert_validation(str: '123')
391
+ assert_validation(str: nil)
392
+ assert_validation({})
393
+ assert_validation(str: '1234') do
394
+ error '/str', 'Matches 2 definitions but should match exactly 1.'
395
+ end
396
+ end
397
+
398
+ def test_any_of_required
399
+ schema do
400
+ any_of! :str_or_int do
401
+ str
402
+ int
403
+ end
404
+ end
405
+
406
+ assert_validation(str_or_int: 'Hello World')
407
+ assert_validation(str_or_int: 42)
408
+ assert_validation(str_or_int: :foo) do
409
+ error '/str_or_int', 'Does not match any anyOf condition.'
410
+ end
411
+ end
412
+
413
+ def test_any_of_optional
414
+ schema do
415
+ any_of? :str_or_int do
416
+ str
417
+ int
418
+ end
419
+ end
420
+
421
+ assert_validation(str_or_int: 'Hello World')
422
+ assert_validation(str_or_int: 42)
423
+ assert_validation(str_or_int: nil)
424
+ assert_validation({})
425
+ assert_validation(str_or_int: :foo) do
426
+ error '/str_or_int', 'Does not match any anyOf condition.'
427
+ end
428
+ end
429
+
430
+ def test_is_not_required
431
+ schema do
432
+ is_not! :foo, required: true do
433
+ str
434
+ end
435
+ end
436
+
437
+ assert_validation(foo: 42)
438
+ assert_validation(foo: true)
439
+ assert_validation(foo: { bar: :baz })
440
+ assert_validation(foo: nil) do
441
+ error '/foo', 'Value must be given.'
442
+ end
443
+ assert_validation(foo: 'string') do
444
+ error '/foo', 'Must not match schema: {"type"=>"string"}.'
445
+ end
446
+ end
447
+
448
+ def test_is_not_optional
449
+ schema do
450
+ is_not? :foo do
451
+ str
452
+ end
453
+ end
454
+
455
+ assert_validation(foo: 42)
456
+ assert_validation(foo: true)
457
+ assert_validation(foo: { bar: :baz })
458
+ assert_validation(foo: nil)
459
+ assert_validation(foo: 'string') do
460
+ error '/foo', 'Must not match schema: {"type"=>"string"}.'
461
+ end
462
+ end
463
+
464
+ # Helper function that checks for all the options if the option is
465
+ # an integer or something else, in which case it needs to raise
466
+ def validate_self_should_error(value_to_check)
467
+ assert_raises_with_message Exceptions::InvalidSchemaError,
468
+ 'Option "min_properties" must be an "integer"' do
469
+ schema :hash, min_properties: value_to_check
470
+ end
471
+
472
+ assert_raises_with_message Exceptions::InvalidSchemaError,
473
+ 'Option "max_properties" must be an "integer"' do
474
+ schema :hash, max_properties: value_to_check
475
+ end
476
+ end
477
+
478
+ def test_validate_self
479
+ assert_raises_with_message Exceptions::InvalidSchemaError,
480
+ 'Pattern properties can\'t be required.' do
481
+ schema :hash do
482
+ str!(/[a-z]+/)
483
+ end
484
+ end
485
+
486
+ validate_self_should_error(1.0)
487
+ validate_self_should_error(4r)
488
+ validate_self_should_error(true)
489
+ validate_self_should_error(false)
490
+ validate_self_should_error((4 + 6i))
491
+ validate_self_should_error('13')
492
+ validate_self_should_error('Lorem ipsum')
493
+
494
+ # rubocop:disable Lint/BooleanSymbol
495
+ assert_raises_with_message Exceptions::InvalidSchemaError,
496
+ 'Option "additional_properties" must be a boolean value' do
497
+ schema :hash, additional_properties: :true
498
+ end
499
+ # rubocop:enable Lint/BooleanSymbol
500
+ end
501
+
502
+ def test_doc_example
503
+ schema :hash do
504
+ scm :address do
505
+ str! :street
506
+ int! :number
507
+ str! :zip
508
+ end
509
+ int? :id
510
+ str! :name
511
+ ref! :address, :address
512
+ ary! :additional_addresses, default: [] do
513
+ ref :address
514
+ end
515
+ ary? :comments, :array, default: [] do
516
+ str
517
+ end
518
+ hsh! :jobs, min_properties: 1 do
519
+ str?(/^[0-9]+$/)
520
+ end
521
+ end
522
+
523
+ assert_validation(
524
+ id: 42,
525
+ name: 'John Doe',
526
+ address: {
527
+ street: 'Silver Street',
528
+ number: 4,
529
+ zip: '38234C'
530
+ },
531
+ additional_addresses: [
532
+ { street: 'Example street', number: 42, zip: '8048' }
533
+ ],
534
+ comments: [
535
+ 'This is a comment'
536
+ ],
537
+ jobs: {
538
+ 2020 => 'Software Engineer'
539
+ }
540
+ )
541
+ end
542
+
543
+ def test_cast_without_additional
544
+ schema :hash do
545
+ str! :foo, format: :integer
546
+ end
547
+
548
+ assert_validation(nil)
549
+ assert_validation(foo: '1')
550
+ assert_cast({ foo: '1' }, { foo: 1 }.with_indifferent_access)
551
+
552
+ assert_validation(foo: '1', bar: '2') do
553
+ error '/', 'Obsolete property "bar".'
554
+ end
555
+
556
+ assert_json(
557
+ type: 'object',
558
+ properties: {
559
+ foo: {
560
+ type: :string,
561
+ format: :integer
562
+ }
563
+ },
564
+ additionalProperties: false,
565
+ required: %i[foo]
566
+ )
567
+ end
568
+
569
+ def test_cast_with_additional
570
+ schema :hash, additional_properties: true do
571
+ str! :foo, format: :integer
572
+ end
573
+
574
+ assert_validation(nil)
575
+ assert_validation(foo: '1')
576
+ assert_cast({ foo: '1' }, { foo: 1 }.with_indifferent_access)
577
+
578
+ assert_validation(foo: '1', bar: nil)
579
+ assert_validation(foo: '1', bar: '2')
580
+ assert_cast({ foo: '1', bar: '2' }, { foo: 1, bar: '2' }.with_indifferent_access)
581
+
582
+ assert_json(
583
+ type: 'object',
584
+ properties: {
585
+ foo: {
586
+ type: :string,
587
+ format: :integer
588
+ }
589
+ },
590
+ additionalProperties: true,
591
+ required: %i[foo]
592
+ )
593
+ end
594
+
595
+ def test_multiple_add_in_schema
596
+ assert_raises_with_message Exceptions::InvalidSchemaError,
597
+ 'You can only use "add" once to specify additional properties.' do
598
+ schema :hash do
599
+ add :integer
600
+ add :string
601
+ end
602
+ end
603
+ end
604
+
605
+ def test_cast_with_additional_in_block
606
+ schema :hash do
607
+ str! :foo, format: :integer
608
+ add :string
609
+ end
610
+
611
+ assert_validation(nil)
612
+ assert_validation(foo: '1')
613
+ assert_cast({ foo: '1' }, { foo: 1 }.with_indifferent_access)
614
+
615
+ assert_validation(foo: '1', bar: nil)
616
+ assert_validation(foo: '1', bar: '2')
617
+ assert_cast({ foo: '1', bar: '2' }, { foo: 1, bar: '2' }.with_indifferent_access)
618
+
619
+ assert_json(
620
+ type: 'object',
621
+ properties: {
622
+ foo: {
623
+ type: :string,
624
+ format: :integer
625
+ }
626
+ },
627
+ additionalProperties: { type: :string },
628
+ required: %i[foo]
629
+ )
630
+ end
631
+
632
+ def test_cast_with_additional_in_block_with_casting
633
+ schema :hash do
634
+ str! :foo, format: :integer
635
+ add :string, format: :integer
636
+ end
637
+
638
+ assert_validation(nil)
639
+ assert_validation(foo: '1')
640
+ assert_cast({ foo: '1' }, { foo: 1 }.with_indifferent_access)
641
+
642
+ assert_validation(foo: '1', bar: nil)
643
+ assert_validation(foo: '1', bar: '2')
644
+ assert_cast({ foo: '1', bar: '2' }, { foo: 1, bar: 2 }.with_indifferent_access)
645
+ end
646
+
647
+ def test_cast_with_additional_any_of
648
+ schema :hash do
649
+ str! :foo, format: :integer
650
+ add :any_of do
651
+ str
652
+ int
653
+ end
654
+ end
655
+
656
+ assert_validation(nil)
657
+ assert_validation(foo: '1')
658
+ assert_cast({ foo: '1' }, { foo: 1 }.with_indifferent_access)
659
+
660
+ assert_validation(foo: '1', bar: nil)
661
+ assert_validation(foo: '1', bar: '2')
662
+ assert_validation(foo: '1', bar: '2', baz: 3)
663
+ assert_validation(foo: '1', bar: '2', baz: 3, qux: [1, 2]) do
664
+ error '/qux', 'Does not match any anyOf condition.'
665
+ end
666
+
667
+ assert_cast({ foo: '1', bar: '2' }, { foo: 1, bar: '2' }.with_indifferent_access)
668
+
669
+ assert_json(
670
+ type: 'object',
671
+ properties: {
672
+ foo: {
673
+ type: :string,
674
+ format: :integer
675
+ }
676
+ },
677
+ additionalProperties: {
678
+ anyOf: [
679
+ { type: :string },
680
+ { type: :integer }
681
+ ]
682
+ },
683
+ required: %i[foo]
684
+ )
685
+ end
686
+
687
+ def test_cast_with_additional_any_of_with_casting
688
+ schema :hash do
689
+ str! :foo, format: :integer
690
+ add :any_of do
691
+ str format: :integer
692
+ str format: :date
693
+ int
694
+ end
695
+ end
696
+
697
+ assert_validation(nil)
698
+ assert_validation(foo: '1')
699
+ assert_cast({ foo: '1' }, { foo: 1 }.with_indifferent_access)
700
+
701
+ assert_validation(foo: '1', bar: nil)
702
+ assert_validation(foo: '1', bar: '2')
703
+ assert_validation(foo: '1', bar: '2', baz: 3)
704
+ assert_validation(foo: '1', bar: '2', baz: 3, qux: [1, 2]) do
705
+ error '/qux', 'Does not match any anyOf condition.'
706
+ end
707
+
708
+ assert_cast({ foo: '1', bar: '2' }, { foo: 1, bar: 2 }.with_indifferent_access)
709
+ assert_cast({ foo: '1', bar: '2', qux: '2020-01-13', asd: 1 }, { foo: 1, bar: 2, qux: Date.new(2020, 1, 13), asd: 1 }.with_indifferent_access)
710
+
711
+ assert_json(
712
+ type: 'object',
713
+ properties: {
714
+ foo: {
715
+ type: :string,
716
+ format: :integer
717
+ }
718
+ },
719
+ additionalProperties: {
720
+ anyOf: [
721
+ {
722
+ type: :string,
723
+ format: :integer
724
+ },
725
+ {
726
+ type: :string,
727
+ format: :date
728
+ },
729
+ {
730
+ type: :integer
731
+ }
732
+ ]
733
+ },
734
+ required: %i[foo]
735
+ )
736
+ end
737
+
738
+ def test_enum_schema
739
+ schema :hash do
740
+ str! :foo, enum: ['bar', 'qux', 123, :faz]
741
+ end
742
+
743
+ assert_json({
744
+ type: :object,
745
+ additionalProperties: false,
746
+ properties: {
747
+ foo: {
748
+ type: :string,
749
+ enum: ['bar', 'qux', 123, :faz]
750
+ }
751
+ },
752
+ required: [:foo]
753
+ })
754
+
755
+ assert_validation(nil)
756
+ assert_validation({ foo: 'bar' })
757
+ assert_validation({ foo: 'qux' })
758
+
759
+ # Even we put those types in the enum, they need to fail the validations,
760
+ # as they are not strings
761
+ assert_validation({ foo: 123 }) do
762
+ error '/foo', 'Invalid type, expected "string".'
763
+ end
764
+ assert_validation({ foo: :faz }) do
765
+ error '/foo', 'Invalid type, expected "string".'
766
+ end
767
+
768
+ # These need to fail validation, as they are not in the enum list
769
+ assert_validation({ foo: 'Lorem ipsum' }) do
770
+ error '/foo', 'Value not included in enum ["bar", "qux", 123, :faz].'
771
+ end
772
+ end
773
+
774
+ def test_with_generic_keywords
775
+ schema :hash, title: 'Hash', description: 'A hash with a description' do
776
+ str! :foo,
777
+ enum: ['bar', 'qux', 123, :faz],
778
+ title: 'A string',
779
+ description: 'A string in the hash',
780
+ examples: [
781
+ 'foo'
782
+ ]
783
+ end
784
+
785
+ assert_json({
786
+ type: :object,
787
+ additionalProperties: false,
788
+ title: 'Hash',
789
+ description: 'A hash with a description',
790
+ properties: {
791
+ foo: {
792
+ type: :string,
793
+ enum: ['bar', 'qux', 123, :faz],
794
+ title: 'A string',
795
+ examples: ['foo'],
796
+ description: 'A string in the hash'
797
+ }
798
+ },
799
+ required: [:foo]
800
+ })
801
+ end
802
+
803
+ def test_hash_with_indifferent_access
804
+ schema :hash do
805
+ str! :foo
806
+ int? :bar
807
+ add :symbol
808
+ end
809
+
810
+ # Test with symbol notation
811
+ hash = ActiveSupport::HashWithIndifferentAccess.new
812
+
813
+ assert_validation(hash) do
814
+ error '/foo', 'Value must be given.'
815
+ end
816
+ hash[:foo] = 'Foo'
817
+ assert_validation(hash)
818
+ hash[:bar] = 123
819
+ assert_validation(hash)
820
+ hash[:qux] = :ruby
821
+ assert_validation(hash)
822
+
823
+ # Test with string notation
824
+ hash = ActiveSupport::HashWithIndifferentAccess.new
825
+
826
+ assert_validation(hash) do
827
+ error '/foo', 'Value must be given.'
828
+ end
829
+ hash['foo'] = 'Foo'
830
+ assert_validation(hash)
831
+ hash['bar'] = 123
832
+ assert_validation(hash)
833
+ hash['qux'] = :ruby
834
+ assert_validation(hash)
835
+ end
836
+
837
+ def test_invalid_schema
838
+ assert_raises_with_message Exceptions::InvalidSchemaError,
839
+ 'Child nodes must have a name.' do
840
+ schema :hash do
841
+ int!
842
+ end
843
+ end
844
+ end
845
+
846
+ def test_schema_with_string_keys
847
+ schema :hash do
848
+ int! 'foo'
849
+ end
850
+
851
+ assert_validation(nil)
852
+ assert_validation({ 'foo' => 42 })
853
+ assert_validation({ foo: 42 })
854
+
855
+ assert_cast({ 'foo' => 42 }, { 'foo' => 42 })
856
+ assert_cast({ foo: 42 }, { foo: 42 }.with_indifferent_access)
857
+
858
+ assert_validation({}) do
859
+ error '/foo', 'Value must be given.'
860
+ end
861
+
862
+ assert_validation({ :foo => 42, 'foo' => 43 }) do
863
+ error '/', 'Has 1 ambiguous properties: [:foo].'
864
+ end
865
+ end
866
+
867
+ def test_schema_with_string_keys_in_data
868
+ schema :hash do
869
+ int! :foo
870
+ end
871
+
872
+ assert_validation(nil)
873
+ assert_validation({ 'foo' => 42 })
874
+ assert_validation({ foo: 42 })
875
+
876
+ assert_cast({ 'foo' => 42 }, { 'foo' => 42 })
877
+ assert_cast({ foo: 42 }, { foo: 42 }.with_indifferent_access)
878
+
879
+ assert_validation({}) do
880
+ error '/foo', 'Value must be given.'
881
+ end
882
+
883
+ assert_validation({ :foo => 42, 'foo' => 43 }) do
884
+ error '/', 'Has 1 ambiguous properties: [:foo].'
885
+ end
886
+ end
887
+
888
+ def test_invalid_options
889
+ assert_raises_with_message Schemacop::Exceptions::InvalidSchemaError, 'Options [:foo] are not allowed for this node.' do
890
+ schema :hash, foo: 'bar' do
891
+ int! :id
892
+ end
893
+ end
894
+ end
895
+
896
+ def test_casting_optional
897
+ schema :hash do
898
+ str? :foo, format: :integer
899
+ end
900
+
901
+ assert_validation(nil)
902
+ assert_validation({})
903
+
904
+ assert_cast(nil, nil)
905
+ assert_cast({}, {}.with_indifferent_access)
906
+ end
907
+
908
+ def test_as_option
909
+ schema :hash do
910
+ int! :foo, as: :bar
911
+ end
912
+
913
+ assert_validation(nil)
914
+ assert_validation({ foo: 42 })
915
+ assert_validation({ bar: 13 }) do
916
+ error '/', 'Obsolete property "bar".'
917
+ error '/foo', 'Value must be given.'
918
+ end
919
+
920
+ assert_validation({ foo: '13' }) do
921
+ error '/foo', 'Invalid type, expected "integer".'
922
+ end
923
+
924
+ assert_cast(nil, nil)
925
+ assert_cast({ foo: 42 }, { bar: 42 }.with_indifferent_access)
926
+ end
927
+
928
+ def test_as_option_overriding
929
+ schema :hash do
930
+ int? :foo, as: :bar
931
+ int? :bar
932
+ end
933
+
934
+ assert_validation(nil)
935
+ assert_validation({})
936
+ assert_validation({ foo: 42 })
937
+ assert_validation({ foo: 42, bar: 13 })
938
+
939
+ assert_validation({ foo: '13' }) do
940
+ error '/foo', 'Invalid type, expected "integer".'
941
+ end
942
+
943
+ # assert_cast(nil, nil)
944
+ assert_cast({ foo: 42 }, { bar: 42 }.with_indifferent_access)
945
+ assert_cast({ foo: 42, bar: 13 }, { bar: 13 }.with_indifferent_access)
946
+ assert_cast({ bar: 13, foo: 42 }, { bar: 13 }.with_indifferent_access)
947
+
948
+ # Swap order
949
+ schema :hash do
950
+ int? :bar
951
+ int! :foo, as: :bar
952
+ end
953
+
954
+ assert_validation(nil)
955
+ assert_validation({ foo: 42 })
956
+ assert_validation({ foo: 42, bar: 13 })
957
+ assert_validation({ bar: 13 }) do
958
+ error '/foo', 'Value must be given.'
959
+ end
960
+
961
+ assert_validation({ foo: '13' }) do
962
+ error '/foo', 'Invalid type, expected "integer".'
963
+ end
964
+
965
+ assert_cast(nil, nil)
966
+ assert_cast({ foo: 42 }, { bar: 42 }.with_indifferent_access)
967
+ assert_cast({ foo: 42, bar: 13 }, { bar: 42 }.with_indifferent_access)
968
+ assert_cast({ bar: 13, foo: 42 }, { bar: 42 }.with_indifferent_access)
969
+ end
970
+ end
971
+ end
972
+ end