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.
- checksums.yaml +4 -4
- data/.gitignore +3 -0
- data/.releaser_config +0 -1
- data/.rubocop.yml +25 -1
- data/.travis.yml +3 -1
- data/CHANGELOG.md +33 -0
- data/README.md +53 -710
- data/README_V2.md +775 -0
- data/README_V3.md +1197 -0
- data/Rakefile +8 -12
- data/VERSION +1 -1
- data/lib/schemacop.rb +35 -36
- data/lib/schemacop/base_schema.rb +37 -0
- data/lib/schemacop/railtie.rb +10 -0
- data/lib/schemacop/schema.rb +1 -60
- data/lib/schemacop/schema2.rb +22 -0
- data/lib/schemacop/schema3.rb +21 -0
- data/lib/schemacop/scoped_env.rb +25 -13
- data/lib/schemacop/v2.rb +25 -0
- data/lib/schemacop/{caster.rb → v2/caster.rb} +16 -2
- data/lib/schemacop/{collector.rb → v2/collector.rb} +5 -2
- data/lib/schemacop/{dupper.rb → v2/dupper.rb} +1 -1
- data/lib/schemacop/{field_node.rb → v2/field_node.rb} +4 -3
- data/lib/schemacop/v2/node.rb +142 -0
- data/lib/schemacop/{node_resolver.rb → v2/node_resolver.rb} +1 -1
- data/lib/schemacop/v2/node_supporting_field.rb +70 -0
- data/lib/schemacop/{node_supporting_type.rb → v2/node_supporting_type.rb} +8 -5
- data/lib/schemacop/{node_with_block.rb → v2/node_with_block.rb} +3 -2
- data/lib/schemacop/v2/root_node.rb +0 -0
- data/lib/schemacop/v2/validator/array_validator.rb +32 -0
- data/lib/schemacop/{validator → v2/validator}/boolean_validator.rb +1 -1
- data/lib/schemacop/v2/validator/float_validator.rb +7 -0
- data/lib/schemacop/v2/validator/hash_validator.rb +37 -0
- data/lib/schemacop/v2/validator/integer_validator.rb +7 -0
- data/lib/schemacop/{validator → v2/validator}/nil_validator.rb +1 -1
- data/lib/schemacop/v2/validator/number_validator.rb +21 -0
- data/lib/schemacop/v2/validator/object_validator.rb +29 -0
- data/lib/schemacop/v2/validator/string_validator.rb +39 -0
- data/lib/schemacop/{validator → v2/validator}/symbol_validator.rb +1 -1
- data/lib/schemacop/v3.rb +45 -0
- data/lib/schemacop/v3/all_of_node.rb +27 -0
- data/lib/schemacop/v3/any_of_node.rb +28 -0
- data/lib/schemacop/v3/array_node.rb +218 -0
- data/lib/schemacop/v3/boolean_node.rb +16 -0
- data/lib/schemacop/v3/combination_node.rb +45 -0
- data/lib/schemacop/v3/context.rb +17 -0
- data/lib/schemacop/v3/dsl_scope.rb +46 -0
- data/lib/schemacop/v3/global_context.rb +114 -0
- data/lib/schemacop/v3/hash_node.rb +258 -0
- data/lib/schemacop/v3/integer_node.rb +13 -0
- data/lib/schemacop/v3/is_not_node.rb +32 -0
- data/lib/schemacop/v3/node.rb +219 -0
- data/lib/schemacop/v3/node_registry.rb +49 -0
- data/lib/schemacop/v3/number_node.rb +18 -0
- data/lib/schemacop/v3/numeric_node.rb +76 -0
- data/lib/schemacop/v3/object_node.rb +40 -0
- data/lib/schemacop/v3/one_of_node.rb +28 -0
- data/lib/schemacop/v3/reference_node.rb +49 -0
- data/lib/schemacop/v3/result.rb +58 -0
- data/lib/schemacop/v3/string_node.rb +124 -0
- data/lib/schemacop/v3/symbol_node.rb +13 -0
- data/schemacop.gemspec +24 -27
- data/test/lib/test_helper.rb +152 -0
- data/test/schemas/nested/group.rb +6 -0
- data/test/schemas/user.rb +7 -0
- data/test/unit/schemacop/v2/casting_test.rb +120 -0
- data/test/unit/schemacop/v2/collector_test.rb +47 -0
- data/test/unit/schemacop/v2/custom_check_test.rb +95 -0
- data/test/unit/schemacop/v2/custom_if_test.rb +97 -0
- data/test/unit/schemacop/v2/defaults_test.rb +95 -0
- data/test/unit/schemacop/v2/empty_test.rb +16 -0
- data/test/unit/schemacop/v2/nil_dis_allow_test.rb +43 -0
- data/test/unit/schemacop/v2/node_resolver_test.rb +28 -0
- data/test/unit/schemacop/v2/short_forms_test.rb +351 -0
- data/test/unit/schemacop/v2/types_test.rb +88 -0
- data/test/unit/schemacop/v2/validator_array_test.rb +99 -0
- data/test/unit/schemacop/v2/validator_boolean_test.rb +17 -0
- data/test/unit/schemacop/v2/validator_float_test.rb +59 -0
- data/test/unit/schemacop/v2/validator_hash_test.rb +95 -0
- data/test/unit/schemacop/v2/validator_integer_test.rb +48 -0
- data/test/unit/schemacop/v2/validator_nil_test.rb +15 -0
- data/test/unit/schemacop/v2/validator_number_test.rb +62 -0
- data/test/unit/schemacop/v2/validator_object_test.rb +141 -0
- data/test/unit/schemacop/v2/validator_string_test.rb +78 -0
- data/test/unit/schemacop/v2/validator_symbol_test.rb +18 -0
- data/test/unit/schemacop/v3/all_of_node_test.rb +198 -0
- data/test/unit/schemacop/v3/any_of_node_test.rb +218 -0
- data/test/unit/schemacop/v3/array_node_test.rb +815 -0
- data/test/unit/schemacop/v3/boolean_node_test.rb +126 -0
- data/test/unit/schemacop/v3/global_context_test.rb +164 -0
- data/test/unit/schemacop/v3/hash_node_test.rb +884 -0
- data/test/unit/schemacop/v3/integer_node_test.rb +323 -0
- data/test/unit/schemacop/v3/is_not_node_test.rb +173 -0
- data/test/unit/schemacop/v3/node_test.rb +148 -0
- data/test/unit/schemacop/v3/number_node_test.rb +292 -0
- data/test/unit/schemacop/v3/object_node_test.rb +170 -0
- data/test/unit/schemacop/v3/one_of_node_test.rb +187 -0
- data/test/unit/schemacop/v3/reference_node_test.rb +351 -0
- data/test/unit/schemacop/v3/string_node_test.rb +334 -0
- data/test/unit/schemacop/v3/symbol_node_test.rb +75 -0
- metadata +157 -150
- data/doc/Schemacop.html +0 -146
- data/doc/Schemacop/ArrayValidator.html +0 -329
- data/doc/Schemacop/BooleanValidator.html +0 -145
- data/doc/Schemacop/Caster.html +0 -379
- data/doc/Schemacop/Collector.html +0 -787
- data/doc/Schemacop/Dupper.html +0 -214
- data/doc/Schemacop/Exceptions.html +0 -115
- data/doc/Schemacop/Exceptions/InvalidSchemaError.html +0 -124
- data/doc/Schemacop/Exceptions/ValidationError.html +0 -124
- data/doc/Schemacop/FieldNode.html +0 -421
- data/doc/Schemacop/FloatValidator.html +0 -158
- data/doc/Schemacop/HashValidator.html +0 -293
- data/doc/Schemacop/IntegerValidator.html +0 -158
- data/doc/Schemacop/NilValidator.html +0 -145
- data/doc/Schemacop/Node.html +0 -1438
- data/doc/Schemacop/NodeResolver.html +0 -258
- data/doc/Schemacop/NodeSupportingField.html +0 -590
- data/doc/Schemacop/NodeSupportingType.html +0 -612
- data/doc/Schemacop/NodeWithBlock.html +0 -289
- data/doc/Schemacop/NumberValidator.html +0 -232
- data/doc/Schemacop/ObjectValidator.html +0 -298
- data/doc/Schemacop/RootNode.html +0 -171
- data/doc/Schemacop/Schema.html +0 -699
- data/doc/Schemacop/StringValidator.html +0 -295
- data/doc/Schemacop/SymbolValidator.html +0 -145
- data/doc/ScopedEnv.html +0 -351
- data/doc/_index.html +0 -379
- data/doc/class_list.html +0 -51
- data/doc/css/common.css +0 -1
- data/doc/css/full_list.css +0 -58
- data/doc/css/style.css +0 -496
- data/doc/file.README.html +0 -833
- data/doc/file_list.html +0 -56
- data/doc/frames.html +0 -17
- data/doc/index.html +0 -833
- data/doc/inheritance.graphml +0 -524
- data/doc/inheritance.pdf +0 -825
- data/doc/js/app.js +0 -303
- data/doc/js/full_list.js +0 -216
- data/doc/js/jquery.js +0 -4
- data/doc/method_list.html +0 -587
- data/doc/top-level-namespace.html +0 -112
- data/lib/schemacop/node.rb +0 -139
- data/lib/schemacop/node_supporting_field.rb +0 -58
- data/lib/schemacop/root_node.rb +0 -4
- data/lib/schemacop/validator/array_validator.rb +0 -30
- data/lib/schemacop/validator/float_validator.rb +0 -5
- data/lib/schemacop/validator/hash_validator.rb +0 -35
- data/lib/schemacop/validator/integer_validator.rb +0 -5
- data/lib/schemacop/validator/number_validator.rb +0 -19
- data/lib/schemacop/validator/object_validator.rb +0 -27
- data/lib/schemacop/validator/string_validator.rb +0 -37
- data/test/casting_test.rb +0 -100
- data/test/collector_test.rb +0 -45
- data/test/custom_check_test.rb +0 -93
- data/test/custom_if_test.rb +0 -95
- data/test/defaults_test.rb +0 -93
- data/test/empty_test.rb +0 -14
- data/test/nil_dis_allow_test.rb +0 -41
- data/test/node_resolver_test.rb +0 -26
- data/test/short_forms_test.rb +0 -349
- data/test/test_helper.rb +0 -13
- data/test/types_test.rb +0 -84
- data/test/validator_array_test.rb +0 -97
- data/test/validator_boolean_test.rb +0 -15
- data/test/validator_float_test.rb +0 -57
- data/test/validator_hash_test.rb +0 -93
- data/test/validator_integer_test.rb +0 -46
- data/test/validator_nil_test.rb +0 -13
- data/test/validator_number_test.rb +0 -60
- data/test/validator_object_test.rb +0 -139
- data/test/validator_string_test.rb +0 -76
- 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,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
|