schemacop 3.0.0.rc0 → 3.0.0.rc5
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/.releaser_config +0 -1
- data/.travis.yml +1 -0
- data/CHANGELOG.md +34 -1
- data/README.md +13 -3
- data/README_V3.md +717 -147
- data/Rakefile +1 -0
- data/VERSION +1 -1
- data/lib/schemacop.rb +1 -0
- data/lib/schemacop/schema2.rb +2 -2
- data/lib/schemacop/scoped_env.rb +1 -1
- data/lib/schemacop/v2.rb +0 -1
- data/lib/schemacop/v2/caster.rb +1 -0
- data/lib/schemacop/v2/node_supporting_field.rb +25 -11
- data/lib/schemacop/v2/node_supporting_type.rb +2 -2
- data/lib/schemacop/v3/array_node.rb +1 -2
- data/lib/schemacop/v3/hash_node.rb +77 -27
- data/lib/schemacop/v3/node.rb +11 -6
- data/lib/schemacop/v3/node_registry.rb +0 -4
- data/lib/schemacop/v3/reference_node.rb +7 -1
- data/lib/schemacop/v3/string_node.rb +15 -7
- data/schemacop.gemspec +7 -4
- data/test/lib/test_helper.rb +17 -2
- data/test/unit/schemacop/v2/casting_test.rb +37 -0
- data/test/unit/schemacop/v2/validator_hash_test.rb +11 -0
- data/test/unit/schemacop/v3/all_of_node_test.rb +1 -2
- data/test/unit/schemacop/v3/any_of_node_test.rb +6 -6
- data/test/unit/schemacop/v3/array_node_test.rb +13 -3
- data/test/unit/schemacop/v3/global_context_test.rb +2 -0
- data/test/unit/schemacop/v3/hash_node_test.rb +211 -14
- data/test/unit/schemacop/v3/node_test.rb +14 -0
- data/test/unit/schemacop/v3/one_of_node_test.rb +6 -6
- data/test/unit/schemacop/v3/reference_node_test.rb +76 -60
- data/test/unit/schemacop/v3/string_node_test.rb +38 -0
- metadata +16 -3
- data/lib/schemacop/v2/root_node.rb +0 -6
data/Rakefile
CHANGED
@@ -18,6 +18,7 @@ task :gemspec do
|
|
18
18
|
# needs access to ActiveSupport::HashWithIndifferentAccess and expects
|
19
19
|
# behavior of that as in version 5 of ActiveSupport.
|
20
20
|
spec.add_dependency 'activesupport', '>= 4.0'
|
21
|
+
spec.add_dependency 'ruby2_keywords', '0.0.4'
|
21
22
|
spec.add_development_dependency 'bundler'
|
22
23
|
spec.add_development_dependency 'rake'
|
23
24
|
spec.add_development_dependency 'minitest'
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
3.0.0.
|
1
|
+
3.0.0.rc5
|
data/lib/schemacop.rb
CHANGED
data/lib/schemacop/schema2.rb
CHANGED
data/lib/schemacop/scoped_env.rb
CHANGED
data/lib/schemacop/v2.rb
CHANGED
@@ -10,7 +10,6 @@ require 'schemacop/v2/node'
|
|
10
10
|
require 'schemacop/v2/node_with_block'
|
11
11
|
require 'schemacop/v2/node_supporting_type'
|
12
12
|
require 'schemacop/v2/field_node'
|
13
|
-
require 'schemacop/v2/root_node'
|
14
13
|
require 'schemacop/v2/node_supporting_field'
|
15
14
|
require 'schemacop/v2/caster'
|
16
15
|
require 'schemacop/v2/dupper'
|
data/lib/schemacop/v2/caster.rb
CHANGED
@@ -15,36 +15,50 @@ module Schemacop::V2
|
|
15
15
|
exec_block(&block)
|
16
16
|
end
|
17
17
|
|
18
|
-
def req?(*args, &block)
|
19
|
-
|
18
|
+
def req?(*args, **kwargs, &block)
|
19
|
+
kwargs ||= {}
|
20
|
+
kwargs[:required] = true
|
21
|
+
kwargs[:allow_nil] = true
|
22
|
+
field(*args, **kwargs, &block)
|
20
23
|
end
|
21
24
|
|
22
|
-
def req!(*args, &block)
|
23
|
-
|
25
|
+
def req!(*args, **kwargs, &block)
|
26
|
+
kwargs ||= {}
|
27
|
+
kwargs[:required] = true
|
28
|
+
kwargs[:allow_nil] = false
|
29
|
+
field(*args, **kwargs, &block)
|
24
30
|
end
|
25
31
|
|
26
32
|
alias req req!
|
27
33
|
|
28
|
-
def opt?(*args, &block)
|
29
|
-
|
34
|
+
def opt?(*args, **kwargs, &block)
|
35
|
+
kwargs ||= {}
|
36
|
+
kwargs[:required] = false
|
37
|
+
kwargs[:allow_nil] = true
|
38
|
+
field(*args, **kwargs, &block)
|
30
39
|
end
|
31
40
|
|
32
|
-
def opt!(*args, &block)
|
33
|
-
|
41
|
+
def opt!(*args, **kwargs, &block)
|
42
|
+
kwargs ||= {}
|
43
|
+
kwargs[:required] = false
|
44
|
+
kwargs[:allow_nil] = false
|
45
|
+
field(*args, **kwargs, &block)
|
34
46
|
end
|
35
47
|
|
36
48
|
alias opt opt?
|
37
49
|
|
38
50
|
protected
|
39
51
|
|
40
|
-
def field(*args,
|
52
|
+
def field(*args, **kwargs, &block)
|
41
53
|
name = args.shift
|
54
|
+
required = kwargs.delete(:required)
|
55
|
+
allow_nil = kwargs.delete(:allow_nil)
|
42
56
|
|
43
57
|
if @fields[name]
|
44
|
-
@fields[name].type(*args, &block)
|
58
|
+
@fields[name].type(*args, **kwargs, &block)
|
45
59
|
elsif args.any?
|
46
60
|
@fields[name] = FieldNode.new(name, required) do
|
47
|
-
type(*args, &block)
|
61
|
+
type(*args, **kwargs, &block)
|
48
62
|
end
|
49
63
|
else
|
50
64
|
@fields[name] = FieldNode.new(name, required, &block)
|
@@ -33,7 +33,7 @@ module Schemacop::V2
|
|
33
33
|
super
|
34
34
|
rescue NoMethodError
|
35
35
|
@types = []
|
36
|
-
type :hash,
|
36
|
+
type :hash, &block
|
37
37
|
end
|
38
38
|
|
39
39
|
# required signature:
|
@@ -73,7 +73,7 @@ module Schemacop::V2
|
|
73
73
|
end
|
74
74
|
|
75
75
|
child = klass.new do
|
76
|
-
self.type(*subtypes, options, &block)
|
76
|
+
self.type(*subtypes, **options, &block)
|
77
77
|
end
|
78
78
|
|
79
79
|
# child = klass.build(options)
|
@@ -172,10 +172,9 @@ module Schemacop
|
|
172
172
|
list_item.present?
|
173
173
|
end
|
174
174
|
|
175
|
-
def item_for_data(data
|
175
|
+
def item_for_data(data)
|
176
176
|
item = children.find { |c| item_matches?(c, data) }
|
177
177
|
return item if item
|
178
|
-
return nil unless force
|
179
178
|
|
180
179
|
fail "Could not find specification for item #{data.inspect}."
|
181
180
|
end
|
@@ -11,6 +11,8 @@ module Schemacop
|
|
11
11
|
|
12
12
|
supports_children(name: true)
|
13
13
|
|
14
|
+
attr_reader :properties
|
15
|
+
|
14
16
|
def self.allowed_options
|
15
17
|
super + ATTRIBUTES - %i[dependencies] + %i[additional_properties]
|
16
18
|
end
|
@@ -19,6 +21,14 @@ module Schemacop
|
|
19
21
|
super + NodeRegistry.dsl_methods(true) + %i[dsl_dep dsl_add]
|
20
22
|
end
|
21
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
|
+
|
22
32
|
def add_child(node)
|
23
33
|
unless node.name
|
24
34
|
fail Exceptions::InvalidSchemaError, 'Child nodes must have a name.'
|
@@ -35,7 +45,7 @@ module Schemacop
|
|
35
45
|
@options[:additional_properties] = create(type, **options, &block)
|
36
46
|
end
|
37
47
|
|
38
|
-
def dsl_dep(source, *targets)
|
48
|
+
def dsl_dep(source, *targets, **_kwargs)
|
39
49
|
@options[:dependencies] ||= {}
|
40
50
|
@options[:dependencies][source] = targets
|
41
51
|
end
|
@@ -54,11 +64,11 @@ module Schemacop
|
|
54
64
|
|
55
65
|
json = {}
|
56
66
|
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?
|
67
|
+
json[:patternProperties] = Hash[pattern_properties.values.map { |p| [self.class.sanitize_exp(p.name), p.as_json] }] if pattern_properties.any?
|
58
68
|
|
59
69
|
# In schemacop, by default, additional properties are not allowed,
|
60
70
|
# the users explicitly need to enable additional properties
|
61
|
-
if options[:additional_properties]
|
71
|
+
if options[:additional_properties].is_a?(TrueClass)
|
62
72
|
json[:additionalProperties] = true
|
63
73
|
elsif options[:additional_properties].is_a?(Node)
|
64
74
|
json[:additionalProperties] = options[:additional_properties].as_json
|
@@ -75,14 +85,6 @@ module Schemacop
|
|
75
85
|
return process_json(ATTRIBUTES, json)
|
76
86
|
end
|
77
87
|
|
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
88
|
def allowed_types
|
87
89
|
{ Hash => :object }
|
88
90
|
end
|
@@ -91,14 +93,23 @@ module Schemacop
|
|
91
93
|
super_data = super
|
92
94
|
return if super_data.nil?
|
93
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
|
+
|
94
105
|
# Validate min_properties #
|
95
|
-
if options[:min_properties] &&
|
96
|
-
result.error "Has #{
|
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]}."
|
97
108
|
end
|
98
109
|
|
99
110
|
# Validate max_properties #
|
100
|
-
if options[:max_properties] &&
|
101
|
-
result.error "Has #{
|
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]}."
|
102
113
|
end
|
103
114
|
|
104
115
|
# Validate specified properties #
|
@@ -106,13 +117,13 @@ module Schemacop
|
|
106
117
|
result.in_path(node.name) do
|
107
118
|
next if node.name.is_a?(Regexp)
|
108
119
|
|
109
|
-
node._validate(
|
120
|
+
node._validate(data_hash[node.name], result: result)
|
110
121
|
end
|
111
122
|
end
|
112
123
|
|
113
124
|
# Validate additional properties #
|
114
125
|
specified_properties = @properties.keys.to_set
|
115
|
-
additional_properties =
|
126
|
+
additional_properties = data_hash.reject { |k, _v| specified_properties.include?(k.to_s) }
|
116
127
|
|
117
128
|
property_patterns = {}
|
118
129
|
|
@@ -130,7 +141,9 @@ module Schemacop
|
|
130
141
|
result.error "Property name #{name.inspect} does not match #{options[:property_names].inspect}."
|
131
142
|
end
|
132
143
|
|
133
|
-
if options[:additional_properties].
|
144
|
+
if options[:additional_properties].is_a?(TrueClass)
|
145
|
+
next
|
146
|
+
elsif options[:additional_properties].is_a?(FalseClass) || options[:additional_properties].blank?
|
134
147
|
match = property_patterns.keys.find { |p| p.match?(name.to_s) }
|
135
148
|
if match
|
136
149
|
result.in_path(name) do
|
@@ -149,7 +162,7 @@ module Schemacop
|
|
149
162
|
# Validate dependencies #
|
150
163
|
options[:dependencies]&.each do |source, targets|
|
151
164
|
targets.each do |target|
|
152
|
-
if
|
165
|
+
if data_hash[source].present? && data_hash[target].blank?
|
153
166
|
result.error "Missing property #{target.to_s.inspect} because #{source.to_s.inspect} is given."
|
154
167
|
end
|
155
168
|
end
|
@@ -161,25 +174,54 @@ module Schemacop
|
|
161
174
|
end
|
162
175
|
|
163
176
|
def cast(data)
|
164
|
-
result = {}
|
177
|
+
result = {}.with_indifferent_access
|
165
178
|
data ||= default
|
166
179
|
return nil if data.nil?
|
167
180
|
|
168
|
-
|
181
|
+
data_hash = data.dup.with_indifferent_access
|
182
|
+
|
183
|
+
property_patterns = {}
|
184
|
+
as_names = []
|
185
|
+
|
169
186
|
@properties.each_value do |prop|
|
170
|
-
|
187
|
+
if prop.name.is_a?(Regexp)
|
188
|
+
property_patterns[prop.name] = prop
|
189
|
+
next
|
190
|
+
end
|
191
|
+
|
192
|
+
as_names << prop.as&.to_s if prop.as.present?
|
193
|
+
|
194
|
+
prop_name = prop.as&.to_s || prop.name
|
195
|
+
|
196
|
+
casted_data = prop.cast(data_hash[prop.name])
|
197
|
+
|
198
|
+
if casted_data.present? || data_hash.include?(prop.name)
|
199
|
+
result[prop_name] = casted_data
|
200
|
+
end
|
171
201
|
|
172
|
-
if result[
|
173
|
-
result.delete(
|
202
|
+
if result[prop_name].nil? && !data_hash.include?(prop.name) && !as_names.include?(prop.name)
|
203
|
+
result.delete(prop_name)
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
# Handle regex properties
|
208
|
+
specified_properties = @properties.keys.to_set
|
209
|
+
additional_properties = data_hash.reject { |k, _v| specified_properties.include?(k.to_s.to_sym) }
|
210
|
+
|
211
|
+
if additional_properties.any? && property_patterns.any?
|
212
|
+
additional_properties.each do |name, additional_property|
|
213
|
+
match_key = property_patterns.keys.find { |p| p.match?(name.to_s) }
|
214
|
+
match = property_patterns[match_key]
|
215
|
+
result[name] = match.cast(additional_property)
|
174
216
|
end
|
175
217
|
end
|
176
218
|
|
177
219
|
# Handle additional properties
|
178
|
-
if options[:additional_properties]
|
179
|
-
result =
|
220
|
+
if options[:additional_properties].is_a?(TrueClass)
|
221
|
+
result = data_hash.merge(result)
|
180
222
|
elsif options[:additional_properties].is_a?(Node)
|
181
223
|
specified_properties = @properties.keys.to_set
|
182
|
-
additional_properties =
|
224
|
+
additional_properties = data_hash.reject { |k, _v| specified_properties.include?(k.to_s.to_sym) }
|
183
225
|
if additional_properties.any?
|
184
226
|
additional_properties_result = {}
|
185
227
|
additional_properties.each do |key, value|
|
@@ -197,6 +239,14 @@ module Schemacop
|
|
197
239
|
def init
|
198
240
|
@properties = {}
|
199
241
|
@options[:type] = :object
|
242
|
+
unless @options[:additional_properties].nil? || @options[:additional_properties].is_a?(TrueClass) || @options[:additional_properties].is_a?(FalseClass)
|
243
|
+
fail Schemacop::Exceptions::InvalidSchemaError, 'Option "additional_properties" must be a boolean value'
|
244
|
+
end
|
245
|
+
|
246
|
+
# Default the additional_properties option to false if it's not given
|
247
|
+
if @options[:additional_properties].nil?
|
248
|
+
@options[:additional_properties] = false
|
249
|
+
end
|
200
250
|
end
|
201
251
|
|
202
252
|
def validate_self
|
data/lib/schemacop/v3/node.rb
CHANGED
@@ -2,6 +2,7 @@ module Schemacop
|
|
2
2
|
module V3
|
3
3
|
class Node
|
4
4
|
attr_reader :name
|
5
|
+
attr_reader :as
|
5
6
|
attr_reader :default
|
6
7
|
attr_reader :title
|
7
8
|
attr_reader :description
|
@@ -32,8 +33,10 @@ module Schemacop
|
|
32
33
|
if options.delete(:cast_str)
|
33
34
|
format = NodeRegistry.name(klass)
|
34
35
|
one_of_options = {
|
35
|
-
required:
|
36
|
-
name:
|
36
|
+
required: options.delete(:required),
|
37
|
+
name: options.delete(:name),
|
38
|
+
as: options.delete(:as),
|
39
|
+
description: options.delete(:description)
|
37
40
|
}
|
38
41
|
node = create(:one_of, **one_of_options) do
|
39
42
|
self.node node
|
@@ -45,7 +48,7 @@ module Schemacop
|
|
45
48
|
end
|
46
49
|
|
47
50
|
def self.allowed_options
|
48
|
-
%i[name required default description examples enum parent options cast_str title]
|
51
|
+
%i[name required default description examples enum parent options cast_str title as]
|
49
52
|
end
|
50
53
|
|
51
54
|
def self.dsl_methods
|
@@ -69,11 +72,13 @@ module Schemacop
|
|
69
72
|
disallowed_options = options.keys - self.class.allowed_options
|
70
73
|
|
71
74
|
if disallowed_options.any?
|
72
|
-
fail "Options #{disallowed_options.inspect} are not allowed for this node."
|
75
|
+
fail Schemacop::Exceptions::InvalidSchemaError, "Options #{disallowed_options.inspect} are not allowed for this node."
|
73
76
|
end
|
74
77
|
|
75
78
|
# Assign attributes #
|
76
79
|
@name = options.delete(:name)
|
80
|
+
@name = @name.to_s unless @name.nil? || @name.is_a?(Regexp)
|
81
|
+
@as = options.delete(:as)
|
77
82
|
@required = !!options.delete(:required)
|
78
83
|
@default = options.delete(:default)
|
79
84
|
@title = options.delete(:title)
|
@@ -90,7 +95,7 @@ module Schemacop
|
|
90
95
|
# Run DSL block #
|
91
96
|
if block_given?
|
92
97
|
unless self.class.supports_children_options
|
93
|
-
fail "Node #{self.class} does not support blocks."
|
98
|
+
fail Schemacop::Exceptions::InvalidSchemaError, "Node #{self.class} does not support blocks."
|
94
99
|
end
|
95
100
|
|
96
101
|
scope = DslScope.new(self)
|
@@ -117,7 +122,7 @@ module Schemacop
|
|
117
122
|
@schemas[name] = create(type, **options, &block)
|
118
123
|
end
|
119
124
|
|
120
|
-
def dsl_node(node)
|
125
|
+
def dsl_node(node, *_args, **_kwargs)
|
121
126
|
add_child node
|
122
127
|
end
|
123
128
|
|
@@ -36,7 +36,13 @@ module Schemacop
|
|
36
36
|
end
|
37
37
|
|
38
38
|
def used_external_schemas
|
39
|
-
|
39
|
+
target_children_schema = target.used_external_schemas
|
40
|
+
|
41
|
+
if schemas.include?(@path)
|
42
|
+
return target_children_schema
|
43
|
+
else
|
44
|
+
return [@path] + target_children_schema
|
45
|
+
end
|
40
46
|
end
|
41
47
|
|
42
48
|
protected
|
@@ -66,21 +66,29 @@ module Schemacop
|
|
66
66
|
end
|
67
67
|
|
68
68
|
def cast(value)
|
69
|
+
if value.present?
|
70
|
+
to_cast = value
|
71
|
+
elsif default.present?
|
72
|
+
to_cast = default
|
73
|
+
else
|
74
|
+
return nil
|
75
|
+
end
|
76
|
+
|
69
77
|
case options[:format]
|
70
78
|
when :boolean
|
71
|
-
return
|
79
|
+
return to_cast == 'true'
|
72
80
|
when :date
|
73
|
-
return Date.parse(
|
81
|
+
return Date.parse(to_cast)
|
74
82
|
when :'date-time'
|
75
|
-
return DateTime.parse(
|
83
|
+
return DateTime.parse(to_cast)
|
76
84
|
when :time
|
77
|
-
Time.parse(
|
85
|
+
Time.parse(to_cast)
|
78
86
|
when :integer
|
79
|
-
return Integer(
|
87
|
+
return Integer(to_cast)
|
80
88
|
when :number
|
81
|
-
return Float(
|
89
|
+
return Float(to_cast)
|
82
90
|
else
|
83
|
-
return
|
91
|
+
return to_cast
|
84
92
|
end
|
85
93
|
end
|
86
94
|
|