schemacop 2.4.7 → 3.0.0.rc0
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/.rubocop.yml +25 -1
- data/.travis.yml +2 -1
- data/CHANGELOG.md +8 -0
- data/README.md +41 -708
- data/README_V2.md +775 -0
- data/README_V3.md +683 -0
- data/Rakefile +8 -12
- data/VERSION +1 -1
- data/lib/schemacop.rb +35 -37
- 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 +26 -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/{node_supporting_field.rb → v2/node_supporting_field.rb} +8 -10
- data/lib/schemacop/{node_supporting_type.rb → v2/node_supporting_type.rb} +6 -3
- data/lib/schemacop/{node_with_block.rb → v2/node_with_block.rb} +3 -2
- data/lib/schemacop/v2/root_node.rb +6 -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 +219 -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 +217 -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 +214 -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 +199 -0
- data/test/unit/schemacop/v3/any_of_node_test.rb +218 -0
- data/test/unit/schemacop/v3/array_node_test.rb +805 -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 +775 -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 +152 -145
- 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/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 -118
- 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,114 @@
|
|
1
|
+
module Schemacop
|
2
|
+
module V3
|
3
|
+
class GlobalContext < Context
|
4
|
+
DSL_METHODS = %i[schema].freeze
|
5
|
+
|
6
|
+
def self.instance
|
7
|
+
@instance ||= new
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.eager_load!
|
11
|
+
instance.eager_load!
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.schemas
|
15
|
+
instance.schemas
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.schema_for(path)
|
19
|
+
instance.schema_for(path)
|
20
|
+
end
|
21
|
+
|
22
|
+
def schema(type = :hash, **options, &block)
|
23
|
+
@current_schemas << Node.create(type, **options, &block)
|
24
|
+
end
|
25
|
+
|
26
|
+
def schema_for(path)
|
27
|
+
path = path.to_sym
|
28
|
+
load_schema(path) unless @eager_loaded
|
29
|
+
@schemas[path]
|
30
|
+
end
|
31
|
+
|
32
|
+
def eager_load!
|
33
|
+
@schemas = {}
|
34
|
+
|
35
|
+
fail "Global context can't be eager loaded more than once." if @eager_loaded
|
36
|
+
|
37
|
+
Schemacop.load_paths.each do |load_path|
|
38
|
+
Dir.glob(File.join(load_path, '**', '*.rb')).sort.each do |file|
|
39
|
+
load_file(file, load_path)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
@eager_loaded = true
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def initialize
|
49
|
+
super
|
50
|
+
@schemas = {}
|
51
|
+
@load_paths_by_schemas = {}
|
52
|
+
@eager_loaded = false
|
53
|
+
@current_virtual_path = nil
|
54
|
+
end
|
55
|
+
|
56
|
+
def path_for(virtual_path)
|
57
|
+
"#{virtual_path.to_s.underscore}.rb"
|
58
|
+
end
|
59
|
+
|
60
|
+
def virtual_path_for(path, load_path)
|
61
|
+
Pathname.new(path).relative_path_from(load_path).to_s.underscore.gsub(/\.rb$/, '').to_sym
|
62
|
+
end
|
63
|
+
|
64
|
+
def load_schema(virtual_path)
|
65
|
+
path = path_for(virtual_path)
|
66
|
+
|
67
|
+
@schemas = schemas.except(virtual_path).freeze
|
68
|
+
@load_paths_by_schemas = @load_paths_by_schemas.except(virtual_path)
|
69
|
+
|
70
|
+
Schemacop.load_paths.each do |load_path|
|
71
|
+
path_in_load_path = File.join(load_path, path)
|
72
|
+
|
73
|
+
if File.exist?(path_in_load_path)
|
74
|
+
load_file(path_in_load_path, load_path)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def load_file(path, load_path)
|
80
|
+
return false unless File.exist?(path)
|
81
|
+
|
82
|
+
# Determine virtual path
|
83
|
+
virtual_path = virtual_path_for(path, load_path)
|
84
|
+
|
85
|
+
# Run file and collect schemas
|
86
|
+
begin
|
87
|
+
@current_schemas = []
|
88
|
+
env = ScopedEnv.new(self, DSL_METHODS)
|
89
|
+
env.instance_eval IO.read(path)
|
90
|
+
rescue StandardError => e
|
91
|
+
fail "Could not load schema #{path.inspect}: #{e.message}"
|
92
|
+
end
|
93
|
+
|
94
|
+
# Load schemas
|
95
|
+
case @current_schemas.size
|
96
|
+
when 0
|
97
|
+
fail "Schema #{path.inspect} does not define any schema."
|
98
|
+
when 1
|
99
|
+
if @schemas.include?(virtual_path)
|
100
|
+
fail "Schema #{virtual_path.to_s.inspect} is defined in both load paths "\
|
101
|
+
"#{@load_paths_by_schemas[virtual_path].inspect} and #{load_path.inspect}."
|
102
|
+
end
|
103
|
+
|
104
|
+
@load_paths_by_schemas[virtual_path] = load_path
|
105
|
+
@schemas = @schemas.merge(virtual_path => @current_schemas.first)
|
106
|
+
else
|
107
|
+
fail "Schema #{path.inspect} defines multiple schemas."
|
108
|
+
end
|
109
|
+
|
110
|
+
return true
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
@@ -0,0 +1,217 @@
|
|
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
|
+
def self.allowed_options
|
15
|
+
super + ATTRIBUTES - %i[dependencies] + %i[additional_properties]
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.dsl_methods
|
19
|
+
super + NodeRegistry.dsl_methods(true) + %i[dsl_dep dsl_add]
|
20
|
+
end
|
21
|
+
|
22
|
+
def add_child(node)
|
23
|
+
unless node.name
|
24
|
+
fail Exceptions::InvalidSchemaError, 'Child nodes must have a name.'
|
25
|
+
end
|
26
|
+
|
27
|
+
@properties[node.name] = node
|
28
|
+
end
|
29
|
+
|
30
|
+
def dsl_add(type, **options, &block)
|
31
|
+
if @options[:additional_properties].is_a?(Node)
|
32
|
+
fail Exceptions::InvalidSchemaError, 'You can only use "add" once to specify additional properties.'
|
33
|
+
end
|
34
|
+
|
35
|
+
@options[:additional_properties] = create(type, **options, &block)
|
36
|
+
end
|
37
|
+
|
38
|
+
def dsl_dep(source, *targets)
|
39
|
+
@options[:dependencies] ||= {}
|
40
|
+
@options[:dependencies][source] = targets
|
41
|
+
end
|
42
|
+
|
43
|
+
def as_json
|
44
|
+
properties = {}
|
45
|
+
pattern_properties = {}
|
46
|
+
|
47
|
+
@properties.each do |name, property|
|
48
|
+
if name.is_a?(Regexp)
|
49
|
+
pattern_properties[name] = property
|
50
|
+
else
|
51
|
+
properties[name] = property
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
json = {}
|
56
|
+
json[:properties] = Hash[properties.values.map { |p| [p.name, p.as_json] }] if properties.any?
|
57
|
+
json[:patternProperties] = Hash[pattern_properties.values.map { |p| [sanitize_exp(p.name), p.as_json] }] if pattern_properties.any?
|
58
|
+
|
59
|
+
# In schemacop, by default, additional properties are not allowed,
|
60
|
+
# the users explicitly need to enable additional properties
|
61
|
+
if options[:additional_properties] == true
|
62
|
+
json[:additionalProperties] = true
|
63
|
+
elsif options[:additional_properties].is_a?(Node)
|
64
|
+
json[:additionalProperties] = options[:additional_properties].as_json
|
65
|
+
else
|
66
|
+
json[:additionalProperties] = false
|
67
|
+
end
|
68
|
+
|
69
|
+
required_properties = @properties.values.select(&:required?).map(&:name)
|
70
|
+
|
71
|
+
if required_properties.any?
|
72
|
+
json[:required] = required_properties
|
73
|
+
end
|
74
|
+
|
75
|
+
return process_json(ATTRIBUTES, json)
|
76
|
+
end
|
77
|
+
|
78
|
+
def sanitize_exp(exp)
|
79
|
+
exp = exp.to_s
|
80
|
+
if exp.start_with?('(?-mix:')
|
81
|
+
exp = exp.to_s.gsub(/^\(\?-mix:/, '').gsub(/\)$/, '')
|
82
|
+
end
|
83
|
+
return exp
|
84
|
+
end
|
85
|
+
|
86
|
+
def allowed_types
|
87
|
+
{ Hash => :object }
|
88
|
+
end
|
89
|
+
|
90
|
+
def _validate(data, result: Result.new)
|
91
|
+
super_data = super
|
92
|
+
return if super_data.nil?
|
93
|
+
|
94
|
+
# Validate min_properties #
|
95
|
+
if options[:min_properties] && super_data.size < options[:min_properties]
|
96
|
+
result.error "Has #{super_data.size} properties but needs at least #{options[:min_properties]}."
|
97
|
+
end
|
98
|
+
|
99
|
+
# Validate max_properties #
|
100
|
+
if options[:max_properties] && super_data.size > options[:max_properties]
|
101
|
+
result.error "Has #{super_data.size} properties but needs at most #{options[:max_properties]}."
|
102
|
+
end
|
103
|
+
|
104
|
+
# Validate specified properties #
|
105
|
+
@properties.each_value do |node|
|
106
|
+
result.in_path(node.name) do
|
107
|
+
next if node.name.is_a?(Regexp)
|
108
|
+
|
109
|
+
node._validate(super_data[node.name], result: result)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
# Validate additional properties #
|
114
|
+
specified_properties = @properties.keys.to_set
|
115
|
+
additional_properties = super_data.reject { |k, _v| specified_properties.include?(k.to_s.to_sym) }
|
116
|
+
|
117
|
+
property_patterns = {}
|
118
|
+
|
119
|
+
@properties.each_value do |property|
|
120
|
+
if property.name.is_a?(Regexp)
|
121
|
+
property_patterns[property.name] = property
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
property_names = options[:property_names]
|
126
|
+
property_names = Regexp.compile(property_names) if property_names
|
127
|
+
|
128
|
+
additional_properties.each do |name, additional_property|
|
129
|
+
if property_names && !property_names.match?(name)
|
130
|
+
result.error "Property name #{name.inspect} does not match #{options[:property_names].inspect}."
|
131
|
+
end
|
132
|
+
|
133
|
+
if options[:additional_properties].blank?
|
134
|
+
match = property_patterns.keys.find { |p| p.match?(name.to_s) }
|
135
|
+
if match
|
136
|
+
result.in_path(name) do
|
137
|
+
property_patterns[match]._validate(additional_property, result: result)
|
138
|
+
end
|
139
|
+
else
|
140
|
+
result.error "Obsolete property #{name.to_s.inspect}."
|
141
|
+
end
|
142
|
+
elsif options[:additional_properties].is_a?(Node)
|
143
|
+
result.in_path(name) do
|
144
|
+
options[:additional_properties]._validate(additional_property, result: result)
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
# Validate dependencies #
|
150
|
+
options[:dependencies]&.each do |source, targets|
|
151
|
+
targets.each do |target|
|
152
|
+
if super_data[source].present? && super_data[target].blank?
|
153
|
+
result.error "Missing property #{target.to_s.inspect} because #{source.to_s.inspect} is given."
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
def children
|
160
|
+
@properties.values
|
161
|
+
end
|
162
|
+
|
163
|
+
def cast(data)
|
164
|
+
result = {}
|
165
|
+
data ||= default
|
166
|
+
return nil if data.nil?
|
167
|
+
|
168
|
+
# TODO: How to handle regex / etc.?
|
169
|
+
@properties.each_value do |prop|
|
170
|
+
result[prop.name] = prop.cast(data[prop.name])
|
171
|
+
|
172
|
+
if result[prop.name].nil? && !data.include?(prop.name)
|
173
|
+
result.delete(prop.name)
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
# Handle additional properties
|
178
|
+
if options[:additional_properties] == true
|
179
|
+
result = data.merge(result)
|
180
|
+
elsif options[:additional_properties].is_a?(Node)
|
181
|
+
specified_properties = @properties.keys.to_set
|
182
|
+
additional_properties = data.reject { |k, _v| specified_properties.include?(k.to_s.to_sym) }
|
183
|
+
if additional_properties.any?
|
184
|
+
additional_properties_result = {}
|
185
|
+
additional_properties.each do |key, value|
|
186
|
+
additional_properties_result[key] = options[:additional_properties].cast(value)
|
187
|
+
end
|
188
|
+
result = additional_properties_result.merge(result)
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
return result
|
193
|
+
end
|
194
|
+
|
195
|
+
protected
|
196
|
+
|
197
|
+
def init
|
198
|
+
@properties = {}
|
199
|
+
@options[:type] = :object
|
200
|
+
end
|
201
|
+
|
202
|
+
def validate_self
|
203
|
+
unless options[:min_properties].nil? || options[:min_properties].is_a?(Integer)
|
204
|
+
fail 'Option "min_properties" must be an "integer"'
|
205
|
+
end
|
206
|
+
|
207
|
+
unless options[:max_properties].nil? || options[:max_properties].is_a?(Integer)
|
208
|
+
fail 'Option "max_properties" must be an "integer"'
|
209
|
+
end
|
210
|
+
|
211
|
+
if @properties.values.any? { |p| p.name.is_a?(Regexp) && p.required? }
|
212
|
+
fail 'Pattern properties can\'t be required.'
|
213
|
+
end
|
214
|
+
end
|
215
|
+
end
|
216
|
+
end
|
217
|
+
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,214 @@
|
|
1
|
+
module Schemacop
|
2
|
+
module V3
|
3
|
+
class Node
|
4
|
+
attr_reader :name
|
5
|
+
attr_reader :default
|
6
|
+
attr_reader :title
|
7
|
+
attr_reader :description
|
8
|
+
attr_reader :options
|
9
|
+
attr_reader :parent
|
10
|
+
|
11
|
+
class_attribute :_supports_children
|
12
|
+
self._supports_children = nil
|
13
|
+
|
14
|
+
def self.supports_children(name: false)
|
15
|
+
self._supports_children = { name: name }
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.supports_children_options
|
19
|
+
_supports_children
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.resolve_class(type)
|
23
|
+
NodeRegistry.find(type)
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.create(type = self, **options, &block)
|
27
|
+
klass = resolve_class(type)
|
28
|
+
fail "Could not find node for type #{type.inspect}." unless klass
|
29
|
+
|
30
|
+
node = klass.new(**options, &block)
|
31
|
+
|
32
|
+
if options.delete(:cast_str)
|
33
|
+
format = NodeRegistry.name(klass)
|
34
|
+
one_of_options = {
|
35
|
+
required: options.delete(:required),
|
36
|
+
name: options.delete(:name)
|
37
|
+
}
|
38
|
+
node = create(:one_of, **one_of_options) do
|
39
|
+
self.node node
|
40
|
+
str format: format, format_options: options
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
return node
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.allowed_options
|
48
|
+
%i[name required default description examples enum parent options cast_str title]
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.dsl_methods
|
52
|
+
%i[dsl_scm dsl_node]
|
53
|
+
end
|
54
|
+
|
55
|
+
def allowed_types
|
56
|
+
{}
|
57
|
+
end
|
58
|
+
|
59
|
+
def used_external_schemas
|
60
|
+
children.map(&:used_external_schemas).flatten.uniq
|
61
|
+
end
|
62
|
+
|
63
|
+
def children
|
64
|
+
[]
|
65
|
+
end
|
66
|
+
|
67
|
+
def initialize(**options, &block)
|
68
|
+
# Check options #
|
69
|
+
disallowed_options = options.keys - self.class.allowed_options
|
70
|
+
|
71
|
+
if disallowed_options.any?
|
72
|
+
fail "Options #{disallowed_options.inspect} are not allowed for this node."
|
73
|
+
end
|
74
|
+
|
75
|
+
# Assign attributes #
|
76
|
+
@name = options.delete(:name)
|
77
|
+
@required = !!options.delete(:required)
|
78
|
+
@default = options.delete(:default)
|
79
|
+
@title = options.delete(:title)
|
80
|
+
@description = options.delete(:description)
|
81
|
+
@examples = options.delete(:examples)
|
82
|
+
@enum = options.delete(:enum)&.to_set
|
83
|
+
@parent = options.delete(:parent)
|
84
|
+
@options = options
|
85
|
+
@schemas = {}
|
86
|
+
|
87
|
+
# Run subclass init #
|
88
|
+
init
|
89
|
+
|
90
|
+
# Run DSL block #
|
91
|
+
if block_given?
|
92
|
+
unless self.class.supports_children_options
|
93
|
+
fail "Node #{self.class} does not support blocks."
|
94
|
+
end
|
95
|
+
|
96
|
+
scope = DslScope.new(self)
|
97
|
+
env = ScopedEnv.new(self, self.class.dsl_methods, scope, :dsl_)
|
98
|
+
env.instance_exec(&block)
|
99
|
+
end
|
100
|
+
|
101
|
+
# Validate self #
|
102
|
+
begin
|
103
|
+
validate_self
|
104
|
+
rescue StandardError => e
|
105
|
+
fail Exceptions::InvalidSchemaError, e.message
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def create(type, **options, &block)
|
110
|
+
options[:parent] = self
|
111
|
+
return Node.create(type, **options, &block)
|
112
|
+
end
|
113
|
+
|
114
|
+
def init; end
|
115
|
+
|
116
|
+
def dsl_scm(name, type = :hash, **options, &block)
|
117
|
+
@schemas[name] = create(type, **options, &block)
|
118
|
+
end
|
119
|
+
|
120
|
+
def dsl_node(node)
|
121
|
+
add_child node
|
122
|
+
end
|
123
|
+
|
124
|
+
def schemas
|
125
|
+
(parent&.schemas || {}).merge(@schemas)
|
126
|
+
end
|
127
|
+
|
128
|
+
def required?
|
129
|
+
@required
|
130
|
+
end
|
131
|
+
|
132
|
+
def as_json
|
133
|
+
process_json([], {})
|
134
|
+
end
|
135
|
+
|
136
|
+
def cast(value)
|
137
|
+
value || default
|
138
|
+
end
|
139
|
+
|
140
|
+
def validate(data)
|
141
|
+
result = Result.new(self, data)
|
142
|
+
_validate(data, result: result)
|
143
|
+
return result
|
144
|
+
end
|
145
|
+
|
146
|
+
protected
|
147
|
+
|
148
|
+
def item_matches?(item, data)
|
149
|
+
item_result = Result.new(self)
|
150
|
+
item._validate(data, result: item_result)
|
151
|
+
return item_result.errors.none?
|
152
|
+
end
|
153
|
+
|
154
|
+
def process_json(attrs, json)
|
155
|
+
if @schemas.any?
|
156
|
+
json[:definitions] = {}
|
157
|
+
@schemas.each do |name, subschema|
|
158
|
+
json[:definitions][name] = subschema.as_json
|
159
|
+
end
|
160
|
+
end
|
161
|
+
attrs.each do |attr|
|
162
|
+
if options.include?(attr)
|
163
|
+
json[attr.to_s.camelize(:lower).to_sym] = options[attr]
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
json[:title] = @title if @title
|
168
|
+
json[:examples] = @examples if @examples
|
169
|
+
json[:description] = @description if @description
|
170
|
+
json[:default] = @default if @default
|
171
|
+
json[:enum] = @enum.to_a if @enum
|
172
|
+
|
173
|
+
return json.as_json
|
174
|
+
end
|
175
|
+
|
176
|
+
def type_assertion_method
|
177
|
+
:is_a?
|
178
|
+
end
|
179
|
+
|
180
|
+
def _validate(data, result:)
|
181
|
+
# Validate nil #
|
182
|
+
if data.nil? && required?
|
183
|
+
result.error 'Value must be given.'
|
184
|
+
return nil
|
185
|
+
end
|
186
|
+
|
187
|
+
# Apply default #
|
188
|
+
if data.nil?
|
189
|
+
if default
|
190
|
+
data = default
|
191
|
+
else
|
192
|
+
return nil
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
# Validate type #
|
197
|
+
if allowed_types.any? && allowed_types.keys.none? { |c| data.send(type_assertion_method, c) }
|
198
|
+
collection = allowed_types.values.map { |t| "\"#{t}\"" }.uniq.sort.join(' or ')
|
199
|
+
result.error %(Invalid type, expected #{collection}.)
|
200
|
+
return nil
|
201
|
+
end
|
202
|
+
|
203
|
+
# Validate enums #
|
204
|
+
if @enum && !@enum.include?(data)
|
205
|
+
result.error "Value not included in enum #{@enum.to_a.inspect}."
|
206
|
+
end
|
207
|
+
|
208
|
+
return data
|
209
|
+
end
|
210
|
+
|
211
|
+
def validate_self; end
|
212
|
+
end
|
213
|
+
end
|
214
|
+
end
|