schemacop 2.4.6 → 3.0.0.rc3

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 (174) 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 +33 -0
  7. data/README.md +53 -710
  8. data/README_V2.md +775 -0
  9. data/README_V3.md +1197 -0
  10. data/Rakefile +8 -12
  11. data/VERSION +1 -1
  12. data/lib/schemacop.rb +35 -36
  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} +16 -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/root_node.rb +0 -0
  30. data/lib/schemacop/v2/validator/array_validator.rb +32 -0
  31. data/lib/schemacop/{validator → v2/validator}/boolean_validator.rb +1 -1
  32. data/lib/schemacop/v2/validator/float_validator.rb +7 -0
  33. data/lib/schemacop/v2/validator/hash_validator.rb +37 -0
  34. data/lib/schemacop/v2/validator/integer_validator.rb +7 -0
  35. data/lib/schemacop/{validator → v2/validator}/nil_validator.rb +1 -1
  36. data/lib/schemacop/v2/validator/number_validator.rb +21 -0
  37. data/lib/schemacop/v2/validator/object_validator.rb +29 -0
  38. data/lib/schemacop/v2/validator/string_validator.rb +39 -0
  39. data/lib/schemacop/{validator → v2/validator}/symbol_validator.rb +1 -1
  40. data/lib/schemacop/v3.rb +45 -0
  41. data/lib/schemacop/v3/all_of_node.rb +27 -0
  42. data/lib/schemacop/v3/any_of_node.rb +28 -0
  43. data/lib/schemacop/v3/array_node.rb +218 -0
  44. data/lib/schemacop/v3/boolean_node.rb +16 -0
  45. data/lib/schemacop/v3/combination_node.rb +45 -0
  46. data/lib/schemacop/v3/context.rb +17 -0
  47. data/lib/schemacop/v3/dsl_scope.rb +46 -0
  48. data/lib/schemacop/v3/global_context.rb +114 -0
  49. data/lib/schemacop/v3/hash_node.rb +258 -0
  50. data/lib/schemacop/v3/integer_node.rb +13 -0
  51. data/lib/schemacop/v3/is_not_node.rb +32 -0
  52. data/lib/schemacop/v3/node.rb +219 -0
  53. data/lib/schemacop/v3/node_registry.rb +49 -0
  54. data/lib/schemacop/v3/number_node.rb +18 -0
  55. data/lib/schemacop/v3/numeric_node.rb +76 -0
  56. data/lib/schemacop/v3/object_node.rb +40 -0
  57. data/lib/schemacop/v3/one_of_node.rb +28 -0
  58. data/lib/schemacop/v3/reference_node.rb +49 -0
  59. data/lib/schemacop/v3/result.rb +58 -0
  60. data/lib/schemacop/v3/string_node.rb +124 -0
  61. data/lib/schemacop/v3/symbol_node.rb +13 -0
  62. data/schemacop.gemspec +24 -27
  63. data/test/lib/test_helper.rb +152 -0
  64. data/test/schemas/nested/group.rb +6 -0
  65. data/test/schemas/user.rb +7 -0
  66. data/test/unit/schemacop/v2/casting_test.rb +120 -0
  67. data/test/unit/schemacop/v2/collector_test.rb +47 -0
  68. data/test/unit/schemacop/v2/custom_check_test.rb +95 -0
  69. data/test/unit/schemacop/v2/custom_if_test.rb +97 -0
  70. data/test/unit/schemacop/v2/defaults_test.rb +95 -0
  71. data/test/unit/schemacop/v2/empty_test.rb +16 -0
  72. data/test/unit/schemacop/v2/nil_dis_allow_test.rb +43 -0
  73. data/test/unit/schemacop/v2/node_resolver_test.rb +28 -0
  74. data/test/unit/schemacop/v2/short_forms_test.rb +351 -0
  75. data/test/unit/schemacop/v2/types_test.rb +88 -0
  76. data/test/unit/schemacop/v2/validator_array_test.rb +99 -0
  77. data/test/unit/schemacop/v2/validator_boolean_test.rb +17 -0
  78. data/test/unit/schemacop/v2/validator_float_test.rb +59 -0
  79. data/test/unit/schemacop/v2/validator_hash_test.rb +95 -0
  80. data/test/unit/schemacop/v2/validator_integer_test.rb +48 -0
  81. data/test/unit/schemacop/v2/validator_nil_test.rb +15 -0
  82. data/test/unit/schemacop/v2/validator_number_test.rb +62 -0
  83. data/test/unit/schemacop/v2/validator_object_test.rb +141 -0
  84. data/test/unit/schemacop/v2/validator_string_test.rb +78 -0
  85. data/test/unit/schemacop/v2/validator_symbol_test.rb +18 -0
  86. data/test/unit/schemacop/v3/all_of_node_test.rb +198 -0
  87. data/test/unit/schemacop/v3/any_of_node_test.rb +218 -0
  88. data/test/unit/schemacop/v3/array_node_test.rb +815 -0
  89. data/test/unit/schemacop/v3/boolean_node_test.rb +126 -0
  90. data/test/unit/schemacop/v3/global_context_test.rb +164 -0
  91. data/test/unit/schemacop/v3/hash_node_test.rb +884 -0
  92. data/test/unit/schemacop/v3/integer_node_test.rb +323 -0
  93. data/test/unit/schemacop/v3/is_not_node_test.rb +173 -0
  94. data/test/unit/schemacop/v3/node_test.rb +148 -0
  95. data/test/unit/schemacop/v3/number_node_test.rb +292 -0
  96. data/test/unit/schemacop/v3/object_node_test.rb +170 -0
  97. data/test/unit/schemacop/v3/one_of_node_test.rb +187 -0
  98. data/test/unit/schemacop/v3/reference_node_test.rb +351 -0
  99. data/test/unit/schemacop/v3/string_node_test.rb +334 -0
  100. data/test/unit/schemacop/v3/symbol_node_test.rb +75 -0
  101. metadata +157 -150
  102. data/doc/Schemacop.html +0 -146
  103. data/doc/Schemacop/ArrayValidator.html +0 -329
  104. data/doc/Schemacop/BooleanValidator.html +0 -145
  105. data/doc/Schemacop/Caster.html +0 -379
  106. data/doc/Schemacop/Collector.html +0 -787
  107. data/doc/Schemacop/Dupper.html +0 -214
  108. data/doc/Schemacop/Exceptions.html +0 -115
  109. data/doc/Schemacop/Exceptions/InvalidSchemaError.html +0 -124
  110. data/doc/Schemacop/Exceptions/ValidationError.html +0 -124
  111. data/doc/Schemacop/FieldNode.html +0 -421
  112. data/doc/Schemacop/FloatValidator.html +0 -158
  113. data/doc/Schemacop/HashValidator.html +0 -293
  114. data/doc/Schemacop/IntegerValidator.html +0 -158
  115. data/doc/Schemacop/NilValidator.html +0 -145
  116. data/doc/Schemacop/Node.html +0 -1438
  117. data/doc/Schemacop/NodeResolver.html +0 -258
  118. data/doc/Schemacop/NodeSupportingField.html +0 -590
  119. data/doc/Schemacop/NodeSupportingType.html +0 -612
  120. data/doc/Schemacop/NodeWithBlock.html +0 -289
  121. data/doc/Schemacop/NumberValidator.html +0 -232
  122. data/doc/Schemacop/ObjectValidator.html +0 -298
  123. data/doc/Schemacop/RootNode.html +0 -171
  124. data/doc/Schemacop/Schema.html +0 -699
  125. data/doc/Schemacop/StringValidator.html +0 -295
  126. data/doc/Schemacop/SymbolValidator.html +0 -145
  127. data/doc/ScopedEnv.html +0 -351
  128. data/doc/_index.html +0 -379
  129. data/doc/class_list.html +0 -51
  130. data/doc/css/common.css +0 -1
  131. data/doc/css/full_list.css +0 -58
  132. data/doc/css/style.css +0 -496
  133. data/doc/file.README.html +0 -833
  134. data/doc/file_list.html +0 -56
  135. data/doc/frames.html +0 -17
  136. data/doc/index.html +0 -833
  137. data/doc/inheritance.graphml +0 -524
  138. data/doc/inheritance.pdf +0 -825
  139. data/doc/js/app.js +0 -303
  140. data/doc/js/full_list.js +0 -216
  141. data/doc/js/jquery.js +0 -4
  142. data/doc/method_list.html +0 -587
  143. data/doc/top-level-namespace.html +0 -112
  144. data/lib/schemacop/node.rb +0 -139
  145. data/lib/schemacop/node_supporting_field.rb +0 -58
  146. data/lib/schemacop/root_node.rb +0 -4
  147. data/lib/schemacop/validator/array_validator.rb +0 -30
  148. data/lib/schemacop/validator/float_validator.rb +0 -5
  149. data/lib/schemacop/validator/hash_validator.rb +0 -35
  150. data/lib/schemacop/validator/integer_validator.rb +0 -5
  151. data/lib/schemacop/validator/number_validator.rb +0 -19
  152. data/lib/schemacop/validator/object_validator.rb +0 -27
  153. data/lib/schemacop/validator/string_validator.rb +0 -37
  154. data/test/casting_test.rb +0 -100
  155. data/test/collector_test.rb +0 -45
  156. data/test/custom_check_test.rb +0 -93
  157. data/test/custom_if_test.rb +0 -95
  158. data/test/defaults_test.rb +0 -93
  159. data/test/empty_test.rb +0 -14
  160. data/test/nil_dis_allow_test.rb +0 -41
  161. data/test/node_resolver_test.rb +0 -26
  162. data/test/short_forms_test.rb +0 -349
  163. data/test/test_helper.rb +0 -13
  164. data/test/types_test.rb +0 -84
  165. data/test/validator_array_test.rb +0 -97
  166. data/test/validator_boolean_test.rb +0 -15
  167. data/test/validator_float_test.rb +0 -57
  168. data/test/validator_hash_test.rb +0 -93
  169. data/test/validator_integer_test.rb +0 -46
  170. data/test/validator_nil_test.rb +0 -13
  171. data/test/validator_number_test.rb +0 -60
  172. data/test/validator_object_test.rb +0 -139
  173. data/test/validator_string_test.rb +0 -76
  174. data/test/validator_symbol_test.rb +0 -16
@@ -0,0 +1,258 @@
1
+ module Schemacop
2
+ module V3
3
+ class HashNode < Node
4
+ ATTRIBUTES = %i[
5
+ type
6
+ min_properties
7
+ max_properties
8
+ dependencies
9
+ property_names
10
+ ].freeze
11
+
12
+ supports_children(name: true)
13
+
14
+ attr_reader :properties
15
+
16
+ def self.allowed_options
17
+ super + ATTRIBUTES - %i[dependencies] + %i[additional_properties]
18
+ end
19
+
20
+ def self.dsl_methods
21
+ super + NodeRegistry.dsl_methods(true) + %i[dsl_dep dsl_add]
22
+ end
23
+
24
+ def self.sanitize_exp(exp)
25
+ exp = exp.to_s
26
+ if exp.start_with?('(?-mix:')
27
+ exp = exp.to_s.gsub(/^\(\?-mix:/, '').gsub(/\)$/, '')
28
+ end
29
+ return exp
30
+ end
31
+
32
+ def add_child(node)
33
+ unless node.name
34
+ fail Exceptions::InvalidSchemaError, 'Child nodes must have a name.'
35
+ end
36
+
37
+ @properties[node.name] = node
38
+ end
39
+
40
+ def dsl_add(type, **options, &block)
41
+ if @options[:additional_properties].is_a?(Node)
42
+ fail Exceptions::InvalidSchemaError, 'You can only use "add" once to specify additional properties.'
43
+ end
44
+
45
+ @options[:additional_properties] = create(type, **options, &block)
46
+ end
47
+
48
+ def dsl_dep(source, *targets, **_kwargs)
49
+ @options[:dependencies] ||= {}
50
+ @options[:dependencies][source] = targets
51
+ end
52
+
53
+ def as_json
54
+ properties = {}
55
+ pattern_properties = {}
56
+
57
+ @properties.each do |name, property|
58
+ if name.is_a?(Regexp)
59
+ pattern_properties[name] = property
60
+ else
61
+ properties[name] = property
62
+ end
63
+ end
64
+
65
+ json = {}
66
+ json[:properties] = Hash[properties.values.map { |p| [p.name, p.as_json] }] if properties.any?
67
+ json[:patternProperties] = Hash[pattern_properties.values.map { |p| [self.class.sanitize_exp(p.name), p.as_json] }] if pattern_properties.any?
68
+
69
+ # In schemacop, by default, additional properties are not allowed,
70
+ # the users explicitly need to enable additional properties
71
+ if options[:additional_properties] == true
72
+ json[:additionalProperties] = true
73
+ elsif options[:additional_properties].is_a?(Node)
74
+ json[:additionalProperties] = options[:additional_properties].as_json
75
+ else
76
+ json[:additionalProperties] = false
77
+ end
78
+
79
+ required_properties = @properties.values.select(&:required?).map(&:name)
80
+
81
+ if required_properties.any?
82
+ json[:required] = required_properties
83
+ end
84
+
85
+ return process_json(ATTRIBUTES, json)
86
+ end
87
+
88
+ def allowed_types
89
+ { Hash => :object }
90
+ end
91
+
92
+ def _validate(data, result: Result.new)
93
+ super_data = super
94
+ return if super_data.nil?
95
+
96
+ original_data_hash = super_data.dup
97
+ data_hash = super_data.with_indifferent_access
98
+
99
+ if original_data_hash.size != data_hash.size
100
+ ambiguous_properties = original_data_hash.keys - data_hash.keys
101
+
102
+ result.error "Has #{ambiguous_properties.size} ambiguous properties: #{ambiguous_properties}."
103
+ end
104
+
105
+ # Validate min_properties #
106
+ if options[:min_properties] && data_hash.size < options[:min_properties]
107
+ result.error "Has #{data_hash.size} properties but needs at least #{options[:min_properties]}."
108
+ end
109
+
110
+ # Validate max_properties #
111
+ if options[:max_properties] && data_hash.size > options[:max_properties]
112
+ result.error "Has #{data_hash.size} properties but needs at most #{options[:max_properties]}."
113
+ end
114
+
115
+ # Validate specified properties #
116
+ @properties.each_value do |node|
117
+ result.in_path(node.name) do
118
+ next if node.name.is_a?(Regexp)
119
+
120
+ node._validate(data_hash[node.name], result: result)
121
+ end
122
+ end
123
+
124
+ # Validate additional properties #
125
+ specified_properties = @properties.keys.to_set
126
+ additional_properties = data_hash.reject { |k, _v| specified_properties.include?(k.to_s) }
127
+
128
+ property_patterns = {}
129
+
130
+ @properties.each_value do |property|
131
+ if property.name.is_a?(Regexp)
132
+ property_patterns[property.name] = property
133
+ end
134
+ end
135
+
136
+ property_names = options[:property_names]
137
+ property_names = Regexp.compile(property_names) if property_names
138
+
139
+ additional_properties.each do |name, additional_property|
140
+ if property_names && !property_names.match?(name)
141
+ result.error "Property name #{name.inspect} does not match #{options[:property_names].inspect}."
142
+ end
143
+
144
+ if options[:additional_properties].blank?
145
+ match = property_patterns.keys.find { |p| p.match?(name.to_s) }
146
+ if match
147
+ result.in_path(name) do
148
+ property_patterns[match]._validate(additional_property, result: result)
149
+ end
150
+ else
151
+ result.error "Obsolete property #{name.to_s.inspect}."
152
+ end
153
+ elsif options[:additional_properties].is_a?(Node)
154
+ result.in_path(name) do
155
+ options[:additional_properties]._validate(additional_property, result: result)
156
+ end
157
+ end
158
+ end
159
+
160
+ # Validate dependencies #
161
+ options[:dependencies]&.each do |source, targets|
162
+ targets.each do |target|
163
+ if data_hash[source].present? && data_hash[target].blank?
164
+ result.error "Missing property #{target.to_s.inspect} because #{source.to_s.inspect} is given."
165
+ end
166
+ end
167
+ end
168
+ end
169
+
170
+ def children
171
+ @properties.values
172
+ end
173
+
174
+ def cast(data)
175
+ result = {}.with_indifferent_access
176
+ data ||= default
177
+ return nil if data.nil?
178
+
179
+ original_data_hash = data.dup
180
+ data_hash = data.with_indifferent_access
181
+
182
+ if original_data_hash.size != data_hash.size
183
+ ambiguous_properties = original_data_hash.keys - data_hash.keys
184
+
185
+ result.error "Has #{ambiguous_properties.size} ambiguous properties: #{ambiguous_properties}."
186
+ end
187
+
188
+ property_patterns = {}
189
+
190
+ @properties.each_value do |prop|
191
+ if prop.name.is_a?(Regexp)
192
+ property_patterns[prop.name] = prop
193
+ next
194
+ end
195
+
196
+ result[prop.as || prop.name] = prop.cast(data_hash[prop.name])
197
+
198
+ if result[prop.as || prop.name].nil? && !data_hash.include?(prop.name)
199
+ result.delete(prop.as || prop.name)
200
+ end
201
+ end
202
+
203
+ # Handle regex properties
204
+ specified_properties = @properties.keys.to_set
205
+ additional_properties = data_hash.reject { |k, _v| specified_properties.include?(k.to_s.to_sym) }
206
+
207
+ if additional_properties.any? && property_patterns.any?
208
+ additional_properties.each do |name, additional_property|
209
+ match_key = property_patterns.keys.find { |p| p.match?(name.to_s) }
210
+ match = property_patterns[match_key]
211
+ result[name] = match.cast(additional_property)
212
+ end
213
+ end
214
+
215
+ # Handle additional properties
216
+ if options[:additional_properties] == true
217
+ result = data_hash.merge(result)
218
+ elsif options[:additional_properties].is_a?(Node)
219
+ specified_properties = @properties.keys.to_set
220
+ additional_properties = data_hash.reject { |k, _v| specified_properties.include?(k.to_s.to_sym) }
221
+ if additional_properties.any?
222
+ additional_properties_result = {}
223
+ additional_properties.each do |key, value|
224
+ additional_properties_result[key] = options[:additional_properties].cast(value)
225
+ end
226
+ result = additional_properties_result.merge(result)
227
+ end
228
+ end
229
+
230
+ return result
231
+ end
232
+
233
+ protected
234
+
235
+ def init
236
+ @properties = {}
237
+ @options[:type] = :object
238
+ unless @options[:additional_properties].nil? || @options[:additional_properties].is_a?(TrueClass) || @options[:additional_properties].is_a?(FalseClass)
239
+ fail Schemacop::Exceptions::InvalidSchemaError, 'Option "additional_properties" must be a boolean value'
240
+ end
241
+ end
242
+
243
+ def validate_self
244
+ unless options[:min_properties].nil? || options[:min_properties].is_a?(Integer)
245
+ fail 'Option "min_properties" must be an "integer"'
246
+ end
247
+
248
+ unless options[:max_properties].nil? || options[:max_properties].is_a?(Integer)
249
+ fail 'Option "max_properties" must be an "integer"'
250
+ end
251
+
252
+ if @properties.values.any? { |p| p.name.is_a?(Regexp) && p.required? }
253
+ fail 'Pattern properties can\'t be required.'
254
+ end
255
+ end
256
+ end
257
+ end
258
+ end
@@ -0,0 +1,13 @@
1
+ module Schemacop
2
+ module V3
3
+ class IntegerNode < NumericNode
4
+ def as_json
5
+ process_json(ATTRIBUTES, type: :integer)
6
+ end
7
+
8
+ def allowed_types
9
+ { Integer => :integer }
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,32 @@
1
+ module Schemacop
2
+ module V3
3
+ class IsNotNode < CombinationNode
4
+ def type
5
+ :not
6
+ end
7
+
8
+ def _validate(data, result:)
9
+ super_data = super
10
+ return if super_data.nil?
11
+
12
+ if matches(super_data).any?
13
+ result.error "Must not match schema: #{@items.first.as_json.as_json.inspect}."
14
+ end
15
+ end
16
+
17
+ def as_json
18
+ process_json([], type => @items.first.as_json)
19
+ end
20
+
21
+ def validate_self
22
+ if @items.count != 1
23
+ fail 'Node "is_not" only allows exactly one item.'
24
+ end
25
+ end
26
+
27
+ def cast(data)
28
+ data
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,219 @@
1
+ module Schemacop
2
+ module V3
3
+ class Node
4
+ attr_reader :name
5
+ attr_reader :as
6
+ attr_reader :default
7
+ attr_reader :title
8
+ attr_reader :description
9
+ attr_reader :options
10
+ attr_reader :parent
11
+
12
+ class_attribute :_supports_children
13
+ self._supports_children = nil
14
+
15
+ def self.supports_children(name: false)
16
+ self._supports_children = { name: name }
17
+ end
18
+
19
+ def self.supports_children_options
20
+ _supports_children
21
+ end
22
+
23
+ def self.resolve_class(type)
24
+ NodeRegistry.find(type)
25
+ end
26
+
27
+ def self.create(type = self, **options, &block)
28
+ klass = resolve_class(type)
29
+ fail "Could not find node for type #{type.inspect}." unless klass
30
+
31
+ node = klass.new(**options, &block)
32
+
33
+ if options.delete(:cast_str)
34
+ format = NodeRegistry.name(klass)
35
+ one_of_options = {
36
+ required: options.delete(:required),
37
+ name: options.delete(:name),
38
+ as: options.delete(:as),
39
+ description: options.delete(:description)
40
+ }
41
+ node = create(:one_of, **one_of_options) do
42
+ self.node node
43
+ str format: format, format_options: options
44
+ end
45
+ end
46
+
47
+ return node
48
+ end
49
+
50
+ def self.allowed_options
51
+ %i[name required default description examples enum parent options cast_str title as]
52
+ end
53
+
54
+ def self.dsl_methods
55
+ %i[dsl_scm dsl_node]
56
+ end
57
+
58
+ def allowed_types
59
+ {}
60
+ end
61
+
62
+ def used_external_schemas
63
+ children.map(&:used_external_schemas).flatten.uniq
64
+ end
65
+
66
+ def children
67
+ []
68
+ end
69
+
70
+ def initialize(**options, &block)
71
+ # Check options #
72
+ disallowed_options = options.keys - self.class.allowed_options
73
+
74
+ if disallowed_options.any?
75
+ fail "Options #{disallowed_options.inspect} are not allowed for this node."
76
+ end
77
+
78
+ # Assign attributes #
79
+ @name = options.delete(:name)
80
+ @name = @name.to_s unless @name.nil? || @name.is_a?(Regexp)
81
+ @as = options.delete(:as)
82
+ @required = !!options.delete(:required)
83
+ @default = options.delete(:default)
84
+ @title = options.delete(:title)
85
+ @description = options.delete(:description)
86
+ @examples = options.delete(:examples)
87
+ @enum = options.delete(:enum)&.to_set
88
+ @parent = options.delete(:parent)
89
+ @options = options
90
+ @schemas = {}
91
+
92
+ # Run subclass init #
93
+ init
94
+
95
+ # Run DSL block #
96
+ if block_given?
97
+ unless self.class.supports_children_options
98
+ fail "Node #{self.class} does not support blocks."
99
+ end
100
+
101
+ scope = DslScope.new(self)
102
+ env = ScopedEnv.new(self, self.class.dsl_methods, scope, :dsl_)
103
+ env.instance_exec(&block)
104
+ end
105
+
106
+ # Validate self #
107
+ begin
108
+ validate_self
109
+ rescue StandardError => e
110
+ fail Exceptions::InvalidSchemaError, e.message
111
+ end
112
+ end
113
+
114
+ def create(type, **options, &block)
115
+ options[:parent] = self
116
+ return Node.create(type, **options, &block)
117
+ end
118
+
119
+ def init; end
120
+
121
+ def dsl_scm(name, type = :hash, **options, &block)
122
+ @schemas[name] = create(type, **options, &block)
123
+ end
124
+
125
+ def dsl_node(node, *_args, **_kwargs)
126
+ add_child node
127
+ end
128
+
129
+ def schemas
130
+ (parent&.schemas || {}).merge(@schemas)
131
+ end
132
+
133
+ def required?
134
+ @required
135
+ end
136
+
137
+ def as_json
138
+ process_json([], {})
139
+ end
140
+
141
+ def cast(value)
142
+ value || default
143
+ end
144
+
145
+ def validate(data)
146
+ result = Result.new(self, data)
147
+ _validate(data, result: result)
148
+ return result
149
+ end
150
+
151
+ protected
152
+
153
+ def item_matches?(item, data)
154
+ item_result = Result.new(self)
155
+ item._validate(data, result: item_result)
156
+ return item_result.errors.none?
157
+ end
158
+
159
+ def process_json(attrs, json)
160
+ if @schemas.any?
161
+ json[:definitions] = {}
162
+ @schemas.each do |name, subschema|
163
+ json[:definitions][name] = subschema.as_json
164
+ end
165
+ end
166
+ attrs.each do |attr|
167
+ if options.include?(attr)
168
+ json[attr.to_s.camelize(:lower).to_sym] = options[attr]
169
+ end
170
+ end
171
+
172
+ json[:title] = @title if @title
173
+ json[:examples] = @examples if @examples
174
+ json[:description] = @description if @description
175
+ json[:default] = @default if @default
176
+ json[:enum] = @enum.to_a if @enum
177
+
178
+ return json.as_json
179
+ end
180
+
181
+ def type_assertion_method
182
+ :is_a?
183
+ end
184
+
185
+ def _validate(data, result:)
186
+ # Validate nil #
187
+ if data.nil? && required?
188
+ result.error 'Value must be given.'
189
+ return nil
190
+ end
191
+
192
+ # Apply default #
193
+ if data.nil?
194
+ if default
195
+ data = default
196
+ else
197
+ return nil
198
+ end
199
+ end
200
+
201
+ # Validate type #
202
+ if allowed_types.any? && allowed_types.keys.none? { |c| data.send(type_assertion_method, c) }
203
+ collection = allowed_types.values.map { |t| "\"#{t}\"" }.uniq.sort.join(' or ')
204
+ result.error %(Invalid type, expected #{collection}.)
205
+ return nil
206
+ end
207
+
208
+ # Validate enums #
209
+ if @enum && !@enum.include?(data)
210
+ result.error "Value not included in enum #{@enum.to_a.inspect}."
211
+ end
212
+
213
+ return data
214
+ end
215
+
216
+ def validate_self; end
217
+ end
218
+ end
219
+ end