json_schema 0.0.7 → 0.0.9
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +9 -0
- data/bin/validate-schema +37 -0
- data/lib/commands/validate_schema.rb +102 -0
- data/lib/json_reference.rb +18 -2
- data/lib/json_schema.rb +1 -0
- data/lib/json_schema/document_store.rb +48 -0
- data/lib/json_schema/parser.rb +36 -14
- data/lib/json_schema/reference_expander.rb +128 -77
- data/lib/json_schema/schema.rb +140 -8
- data/lib/json_schema/validator.rb +22 -7
- data/schemas/hyper-schema.json +168 -0
- data/schemas/schema.json +150 -0
- data/test/commands/validate_schema_test.rb +103 -0
- data/test/json_reference/reference_test.rb +45 -0
- data/test/json_schema/parser_test.rb +18 -1
- data/test/json_schema/reference_expander_test.rb +81 -39
- data/test/json_schema/validator_test.rb +60 -4
- metadata +11 -3
data/README.md
CHANGED
@@ -2,6 +2,15 @@
|
|
2
2
|
|
3
3
|
A JSON Schema V4 and Hyperschema V4 parser and validator.
|
4
4
|
|
5
|
+
Validate some data based on a JSON Schema:
|
6
|
+
|
7
|
+
```
|
8
|
+
gem install json_schema
|
9
|
+
validate-schema schema.json data.json
|
10
|
+
```
|
11
|
+
|
12
|
+
## Programmatic
|
13
|
+
|
5
14
|
``` ruby
|
6
15
|
require "json"
|
7
16
|
require "json_schema"
|
data/bin/validate-schema
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "json"
|
4
|
+
require "optparse"
|
5
|
+
require_relative "../lib/commands/validate_schema"
|
6
|
+
|
7
|
+
def print_usage!
|
8
|
+
$stderr.puts "Usage: validate-schema <schema> <data>, ..."
|
9
|
+
$stderr.puts " validate-schema -d <data>, ..."
|
10
|
+
end
|
11
|
+
|
12
|
+
command = Commands::ValidateSchema.new
|
13
|
+
|
14
|
+
OptionParser.new { |opts|
|
15
|
+
opts.on("-d", "--detect", "Detect schema from $schema") do
|
16
|
+
command.detect = true
|
17
|
+
|
18
|
+
# mix in common schemas for convenience
|
19
|
+
command.extra_schemas += ["schema.json", "hyper-schema.json"].
|
20
|
+
map { |f| File.expand_path(f, __FILE__ + "/../../schemas") }
|
21
|
+
end
|
22
|
+
opts.on("-s", "--schema SCHEMA", "Additional schema to use for references") do |s|
|
23
|
+
command.extra_schemas << s
|
24
|
+
end
|
25
|
+
}.parse!
|
26
|
+
|
27
|
+
success = command.run(ARGV.dup)
|
28
|
+
|
29
|
+
if success
|
30
|
+
command.messages.each { |m| $stdout.puts(m) }
|
31
|
+
elsif !command.errors.empty?
|
32
|
+
command.errors.each { |e| $stderr.puts(File.basename(__FILE__) + ": " + e) }
|
33
|
+
exit(1)
|
34
|
+
else
|
35
|
+
print_usage!
|
36
|
+
exit(1)
|
37
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
require_relative "../json_schema"
|
2
|
+
|
3
|
+
module Commands
|
4
|
+
class ValidateSchema
|
5
|
+
attr_accessor :detect
|
6
|
+
attr_accessor :extra_schemas
|
7
|
+
|
8
|
+
attr_accessor :errors
|
9
|
+
attr_accessor :messages
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
@detect = false
|
13
|
+
@extra_schemas = []
|
14
|
+
|
15
|
+
@errors = []
|
16
|
+
@messages = []
|
17
|
+
end
|
18
|
+
|
19
|
+
def run(argv)
|
20
|
+
return false if !initialize_store
|
21
|
+
|
22
|
+
if !detect
|
23
|
+
return false if !(schema_file = argv.shift)
|
24
|
+
return false if !(schema = parse(schema_file))
|
25
|
+
end
|
26
|
+
|
27
|
+
# if there are no remaining files in arguments, also a problem
|
28
|
+
return false if argv.count < 1
|
29
|
+
|
30
|
+
argv.each do |data_file|
|
31
|
+
return false if !check_file(data_file)
|
32
|
+
data = JSON.parse(File.read(data_file))
|
33
|
+
|
34
|
+
if detect
|
35
|
+
if !(schema_uri = data["$schema"])
|
36
|
+
@errors = ["#{data_file}: No $schema tag for detection."]
|
37
|
+
return false
|
38
|
+
end
|
39
|
+
|
40
|
+
if !(schema = @store.lookup_uri(schema_uri))
|
41
|
+
@errors = ["#{data_file}: Unknown $schema, try specifying one with -s."]
|
42
|
+
return false
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
valid, errors = schema.validate(data)
|
47
|
+
|
48
|
+
if valid
|
49
|
+
@messages += ["#{data_file} is valid."]
|
50
|
+
else
|
51
|
+
errors = ["Invalid."] + errors.map { |e| e.message }
|
52
|
+
@errors += errors.map { |e| "#{data_file}: #{e}" }
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
@errors.empty?
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
def check_file(file)
|
62
|
+
if !File.exists?(file)
|
63
|
+
@errors = ["#{file}: No such file or directory."]
|
64
|
+
false
|
65
|
+
else
|
66
|
+
true
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def initialize_store
|
71
|
+
@store = JsonSchema::DocumentStore.new
|
72
|
+
extra_schemas.each do |extra_schema|
|
73
|
+
if !(extra_schema = parse(extra_schema))
|
74
|
+
return false
|
75
|
+
end
|
76
|
+
@store.add_uri_reference(extra_schema.uri, extra_schema)
|
77
|
+
end
|
78
|
+
true
|
79
|
+
end
|
80
|
+
|
81
|
+
def parse(file)
|
82
|
+
return nil if !check_file(file)
|
83
|
+
|
84
|
+
parser = JsonSchema::Parser.new
|
85
|
+
if !(schema = parser.parse(JSON.parse(File.read(file))))
|
86
|
+
@errors = ["Schema is invalid."] + parser.errors.map { |e| e.message }
|
87
|
+
@errors.map! { |e| "#{file}: #{e}" }
|
88
|
+
return nil
|
89
|
+
end
|
90
|
+
|
91
|
+
expander = JsonSchema::ReferenceExpander.new
|
92
|
+
if !expander.expand(schema, store: @store)
|
93
|
+
@errors = ["Could not expand schema references."] +
|
94
|
+
expander.errors.map { |e| e.message }
|
95
|
+
@errors.map! { |e| "#{file}: #{e}" }
|
96
|
+
return nil
|
97
|
+
end
|
98
|
+
|
99
|
+
schema
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
data/lib/json_reference.rb
CHANGED
@@ -1,8 +1,14 @@
|
|
1
|
-
require "json_pointer"
|
2
1
|
require "uri"
|
2
|
+
require_relative "json_pointer"
|
3
3
|
|
4
4
|
module JsonReference
|
5
|
+
def self.reference(ref)
|
6
|
+
Reference.new(ref)
|
7
|
+
end
|
8
|
+
|
5
9
|
class Reference
|
10
|
+
include Comparable
|
11
|
+
|
6
12
|
attr_accessor :pointer
|
7
13
|
attr_accessor :uri
|
8
14
|
|
@@ -14,12 +20,22 @@ module JsonReference
|
|
14
20
|
if uri && !uri.empty?
|
15
21
|
@uri = URI.parse(uri)
|
16
22
|
end
|
23
|
+
@pointer ||= ""
|
17
24
|
else
|
18
25
|
@pointer = ref
|
19
26
|
end
|
20
27
|
|
21
|
-
# normalize pointers by prepending "#"
|
28
|
+
# normalize pointers by prepending "#" and stripping trailing "/"
|
22
29
|
@pointer = "#" + @pointer
|
30
|
+
@pointer = @pointer.chomp("/")
|
31
|
+
end
|
32
|
+
|
33
|
+
def <=>(other)
|
34
|
+
to_s <=> other.to_s
|
35
|
+
end
|
36
|
+
|
37
|
+
def inspect
|
38
|
+
"\#<JsonReference::Reference #{to_s}>"
|
23
39
|
end
|
24
40
|
|
25
41
|
# Given the document addressed by #uri, resolves the JSON Pointer part of
|
data/lib/json_schema.rb
CHANGED
@@ -0,0 +1,48 @@
|
|
1
|
+
module JsonSchema
|
2
|
+
# The document store helps resolve URI-based JSON pointers by storing IDs
|
3
|
+
# that we've seen in the schema.
|
4
|
+
#
|
5
|
+
# Each URI tuple also contains a pointer map that helps speed up expansions
|
6
|
+
# that have already happened and handles cyclic dependencies. Store a
|
7
|
+
# reference to the top-level schema before doing anything else.
|
8
|
+
class DocumentStore
|
9
|
+
def initialize
|
10
|
+
@uri_map = {}
|
11
|
+
end
|
12
|
+
|
13
|
+
def add_pointer_reference(uri, path, schema)
|
14
|
+
raise "can't add nil URI" if uri.nil?
|
15
|
+
|
16
|
+
if !@uri_map[uri][:pointer_map].key?(path)
|
17
|
+
@uri_map[uri][:pointer_map][path] = schema
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def add_uri_reference(uri, schema)
|
22
|
+
raise "can't add nil URI" if uri.nil?
|
23
|
+
|
24
|
+
# Children without an ID keep the same URI as their parents. So since we
|
25
|
+
# traverse trees from top to bottom, just keep the first reference.
|
26
|
+
if !@uri_map.key?(uri)
|
27
|
+
@uri_map[uri] = {
|
28
|
+
pointer_map: {
|
29
|
+
JsonReference.reference("#").to_s => schema
|
30
|
+
},
|
31
|
+
schema: schema
|
32
|
+
}
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def lookup_pointer(uri, pointer)
|
37
|
+
@uri_map[uri][:pointer_map][pointer]
|
38
|
+
end
|
39
|
+
|
40
|
+
def lookup_uri(uri)
|
41
|
+
if @uri_map[uri]
|
42
|
+
@uri_map[uri][:schema]
|
43
|
+
else
|
44
|
+
nil
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
data/lib/json_schema/parser.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
|
1
|
+
require_relative "../json_reference"
|
2
2
|
|
3
3
|
module JsonSchema
|
4
4
|
class Parser
|
@@ -45,20 +45,38 @@ module JsonSchema
|
|
45
45
|
def build_uri(id, parent_uri)
|
46
46
|
# kill any trailing slashes
|
47
47
|
if id
|
48
|
-
|
49
|
-
|
50
|
-
|
48
|
+
# may look like: http://json-schema.org/draft-04/hyper-schema#
|
49
|
+
uri = URI.parse(id)
|
50
|
+
# make sure there is no `#` suffix
|
51
|
+
uri.fragment = nil
|
52
|
+
# if id is defined as absolute, the schema's URI stays absolute
|
53
|
+
if uri.absolute? || uri.path[0] == "/"
|
54
|
+
uri.to_s.chomp("/")
|
55
|
+
# otherwise build it according to the parent's URI
|
56
|
+
elsif parent_uri
|
57
|
+
# make sure we don't end up with duplicate slashes
|
58
|
+
parent_uri = parent_uri.chomp("/")
|
59
|
+
parent_uri + "/" + id
|
60
|
+
else
|
61
|
+
"/"
|
62
|
+
end
|
51
63
|
# if id is missing, it's defined as its parent schema's URI
|
52
|
-
|
64
|
+
elsif parent_uri
|
53
65
|
parent_uri
|
54
|
-
# if id is defined as absolute, the schema's URI stays absolute
|
55
|
-
elsif id[0] == "/"
|
56
|
-
id
|
57
|
-
# otherwise build it according to the parent's URI
|
58
66
|
else
|
59
|
-
|
60
|
-
|
61
|
-
|
67
|
+
"/"
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def parse_additional_properties(schema)
|
72
|
+
if schema.additional_properties
|
73
|
+
# an object indicates a schema that will be used to parse any
|
74
|
+
# properties not listed in `properties`
|
75
|
+
if schema.additional_properties.is_a?(Hash)
|
76
|
+
schema.additional_properties =
|
77
|
+
parse_data(schema.additional_properties, schema)
|
78
|
+
end
|
79
|
+
# otherwise, leave as boolean
|
62
80
|
end
|
63
81
|
end
|
64
82
|
|
@@ -201,8 +219,11 @@ module JsonSchema
|
|
201
219
|
schema.data = data
|
202
220
|
schema.id = validate_type(schema, [String], "id")
|
203
221
|
|
222
|
+
# any parsed schema is automatically expanded
|
223
|
+
schema.expanded = true
|
224
|
+
|
204
225
|
# build URI early so we can reference it in errors
|
205
|
-
schema.uri
|
226
|
+
schema.uri = build_uri(schema.id, parent ? parent.uri : nil)
|
206
227
|
|
207
228
|
schema.title = validate_type(schema, [String], "title")
|
208
229
|
schema.description = validate_type(schema, [String], "description")
|
@@ -235,7 +256,7 @@ module JsonSchema
|
|
235
256
|
|
236
257
|
# validation: object
|
237
258
|
schema.additional_properties =
|
238
|
-
validate_type(schema, BOOLEAN, "additionalProperties")
|
259
|
+
validate_type(schema, BOOLEAN + [Hash], "additionalProperties")
|
239
260
|
schema.dependencies = validate_type(schema, [Hash], "dependencies") || {}
|
240
261
|
schema.max_properties = validate_type(schema, [Integer], "maxProperties")
|
241
262
|
schema.min_properties = validate_type(schema, [Integer], "minProperties")
|
@@ -256,6 +277,7 @@ module JsonSchema
|
|
256
277
|
schema.path_start = validate_type(schema, [String], "pathStart")
|
257
278
|
schema.read_only = validate_type(schema, BOOLEAN, "readOnly")
|
258
279
|
|
280
|
+
parse_additional_properties(schema)
|
259
281
|
parse_all_of(schema)
|
260
282
|
parse_any_of(schema)
|
261
283
|
parse_one_of(schema)
|
@@ -1,35 +1,22 @@
|
|
1
|
-
require "json_schema/parser"
|
2
1
|
require "set"
|
3
2
|
|
4
3
|
module JsonSchema
|
5
4
|
class ReferenceExpander
|
6
5
|
attr_accessor :errors
|
7
6
|
|
8
|
-
def expand(schema)
|
7
|
+
def expand(schema, options = {})
|
9
8
|
@errors = []
|
10
9
|
@schema = schema
|
11
|
-
@store
|
12
|
-
@unresolved_refs = Set.new
|
13
|
-
last_num_unresolved_refs = 0
|
10
|
+
@store = options[:store] ||= DocumentStore.new
|
14
11
|
|
15
|
-
|
16
|
-
traverse_schema(schema)
|
12
|
+
@store.add_uri_reference("/", schema)
|
17
13
|
|
18
|
-
|
19
|
-
if @unresolved_refs.count == 0
|
20
|
-
break
|
21
|
-
end
|
22
|
-
|
23
|
-
# a new traversal pass still hasn't managed to resolved anymore
|
24
|
-
# references; we're out of luck
|
25
|
-
if @unresolved_refs.count == last_num_unresolved_refs
|
26
|
-
refs = @unresolved_refs.to_a.join(", ")
|
27
|
-
message = %{Couldn't resolve references (possible circular dependency): #{refs}.}
|
28
|
-
@errors << SchemaError.new(schema, message)
|
29
|
-
break
|
30
|
-
end
|
14
|
+
traverse_schema(schema)
|
31
15
|
|
32
|
-
|
16
|
+
refs = unresolved_refs(schema).sort
|
17
|
+
if refs.count > 0
|
18
|
+
message = %{Couldn't resolve references: #{refs.to_a.join(", ")}.}
|
19
|
+
@errors << SchemaError.new(schema, message)
|
33
20
|
end
|
34
21
|
|
35
22
|
@errors.count == 0
|
@@ -44,65 +31,101 @@ module JsonSchema
|
|
44
31
|
|
45
32
|
private
|
46
33
|
|
47
|
-
def dereference(
|
48
|
-
ref =
|
34
|
+
def dereference(ref_schema, ref_stack)
|
35
|
+
ref = ref_schema.reference
|
36
|
+
|
37
|
+
# detects a reference cycle
|
38
|
+
if ref_stack.include?(ref)
|
39
|
+
message = %{Reference cycle detected: #{ref_stack.sort.join(", ")}.}
|
40
|
+
@errors << SchemaError.new(ref_schema, message)
|
41
|
+
return false
|
42
|
+
end
|
43
|
+
|
44
|
+
new_schema = resolve_reference(ref_schema)
|
45
|
+
return false unless new_schema
|
46
|
+
|
47
|
+
# if the reference resolved to a new reference we need to continue
|
48
|
+
# dereferencing until we either hit a non-reference schema, or a
|
49
|
+
# reference which is already resolved
|
50
|
+
if new_schema.reference && !new_schema.expanded?
|
51
|
+
success = dereference(new_schema, ref_stack + [ref])
|
52
|
+
return false unless success
|
53
|
+
end
|
54
|
+
|
55
|
+
# copy new schema into existing one while preserving parent
|
56
|
+
parent = ref_schema.parent
|
57
|
+
ref_schema.copy_from(new_schema)
|
58
|
+
ref_schema.parent = parent
|
59
|
+
|
60
|
+
true
|
61
|
+
end
|
62
|
+
|
63
|
+
def resolve_pointer(ref_schema, uri_path, resolved_schema)
|
64
|
+
ref = ref_schema.reference
|
65
|
+
|
66
|
+
# we've already evaluated this precise URI/pointer combination before
|
67
|
+
if !(new_schema = @store.lookup_pointer(uri_path, ref.pointer.to_s))
|
68
|
+
data = JsonPointer.evaluate(resolved_schema.data, ref.pointer)
|
69
|
+
|
70
|
+
# couldn't resolve pointer within known schema; that's an error
|
71
|
+
if data.nil?
|
72
|
+
message = %{Couldn't resolve pointer "#{ref.pointer}".}
|
73
|
+
@errors << SchemaError.new(resolved_schema, message)
|
74
|
+
return
|
75
|
+
end
|
76
|
+
|
77
|
+
# parse a new schema and use the same parent node
|
78
|
+
new_schema = Parser.new.parse(data, ref_schema.parent)
|
79
|
+
|
80
|
+
# add the reference into our document store right away; it will
|
81
|
+
# eventually be fully expanded
|
82
|
+
@store.add_pointer_reference(uri_path, ref.pointer.to_s, new_schema)
|
83
|
+
else
|
84
|
+
# insert a clone record so that the expander knows to expand it when
|
85
|
+
# the schema traversal is finished
|
86
|
+
new_schema.clones << ref_schema
|
87
|
+
end
|
88
|
+
|
89
|
+
new_schema
|
90
|
+
end
|
91
|
+
|
92
|
+
def resolve_reference(ref_schema)
|
93
|
+
ref = ref_schema.reference
|
49
94
|
uri = ref.uri
|
50
95
|
|
51
96
|
if uri && uri.host
|
52
97
|
scheme = uri.scheme || "http"
|
53
|
-
|
54
|
-
|
98
|
+
# allow resolution if something we've already parsed has claimed the
|
99
|
+
# full URL
|
100
|
+
if @store.lookup_uri(uri.to_s)
|
101
|
+
resolve_uri(ref_schema, uri.to_s)
|
102
|
+
else
|
103
|
+
message =
|
104
|
+
%{Reference resolution over #{scheme} is not currently supported.}
|
105
|
+
@errors << SchemaError.new(ref_schema, message)
|
106
|
+
nil
|
107
|
+
end
|
55
108
|
# absolute
|
56
109
|
elsif uri && uri.path[0] == "/"
|
57
|
-
|
110
|
+
resolve_uri(ref_schema, uri.path)
|
58
111
|
# relative
|
59
112
|
elsif uri
|
60
113
|
# build an absolute path using the URI of the current schema
|
61
|
-
schema_uri =
|
62
|
-
|
114
|
+
schema_uri = ref_schema.uri.chomp("/")
|
115
|
+
resolve_uri(ref_schema, schema_uri + "/" + uri.path)
|
63
116
|
# just a JSON Pointer -- resolve against schema root
|
64
117
|
else
|
65
|
-
|
66
|
-
end
|
67
|
-
end
|
68
|
-
|
69
|
-
def evaluate(schema, schema_context, ref)
|
70
|
-
data = JsonPointer.evaluate(schema_context.data, ref.pointer)
|
71
|
-
|
72
|
-
# couldn't resolve pointer within known schema; that's an error
|
73
|
-
if data.nil?
|
74
|
-
message = %{Couldn't resolve pointer "#{ref.pointer}".}
|
75
|
-
@errors << SchemaError.new(schema_context, message)
|
76
|
-
return
|
77
|
-
end
|
78
|
-
|
79
|
-
# this counts as a resolution
|
80
|
-
@unresolved_refs.delete(ref.to_s)
|
81
|
-
|
82
|
-
# parse a new schema and use the same parent node
|
83
|
-
new_schema = Parser.new.parse(data, schema.parent)
|
84
|
-
|
85
|
-
# mark a new unresolved reference if the schema we got back is also a
|
86
|
-
# reference
|
87
|
-
if new_schema.reference
|
88
|
-
@unresolved_refs.add(new_schema.reference.to_s)
|
118
|
+
resolve_pointer(ref_schema, "/", @schema)
|
89
119
|
end
|
90
|
-
|
91
|
-
# copy new schema into existing one while preserving parent
|
92
|
-
parent = schema.parent
|
93
|
-
schema.copy_from(new_schema)
|
94
|
-
schema.parent = parent
|
95
|
-
|
96
|
-
new_schema
|
97
120
|
end
|
98
121
|
|
99
|
-
def
|
100
|
-
if
|
101
|
-
|
122
|
+
def resolve_uri(ref_schema, uri_path)
|
123
|
+
if schema = @store.lookup_uri(uri_path)
|
124
|
+
resolve_pointer(ref_schema, uri_path, schema)
|
102
125
|
else
|
103
|
-
|
104
|
-
@
|
105
|
-
|
126
|
+
message = %{Couldn't resolve URI: #{uri_path}.}
|
127
|
+
@errors << SchemaError.new(ref_schema, message)
|
128
|
+
nil
|
106
129
|
end
|
107
130
|
end
|
108
131
|
|
@@ -116,17 +139,23 @@ module JsonSchema
|
|
116
139
|
schema.pattern_properties.each { |_, s| yielder << s }
|
117
140
|
schema.properties.each { |_, s| yielder << s }
|
118
141
|
|
142
|
+
if additional = schema.additional_properties
|
143
|
+
if additional.is_a?(Schema)
|
144
|
+
yielder << additional
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
119
148
|
if schema.not
|
120
149
|
yielder << schema.not
|
121
150
|
end
|
122
151
|
|
123
152
|
# can either be a single schema (list validation) or multiple (tuple
|
124
153
|
# validation)
|
125
|
-
if schema.items
|
126
|
-
if
|
127
|
-
|
154
|
+
if items = schema.items
|
155
|
+
if items.is_a?(Array)
|
156
|
+
items.each { |s| yielder << s }
|
128
157
|
else
|
129
|
-
yielder <<
|
158
|
+
yielder << items
|
130
159
|
end
|
131
160
|
end
|
132
161
|
|
@@ -138,18 +167,40 @@ module JsonSchema
|
|
138
167
|
end
|
139
168
|
end
|
140
169
|
|
141
|
-
def
|
142
|
-
#
|
143
|
-
|
144
|
-
|
145
|
-
|
170
|
+
def unresolved_refs(schema)
|
171
|
+
# prevent endless recursion
|
172
|
+
return [] unless schema.original?
|
173
|
+
|
174
|
+
schema_children(schema).reduce([]) do |arr, subschema|
|
175
|
+
if !subschema.expanded?
|
176
|
+
arr += [subschema.reference]
|
177
|
+
else
|
178
|
+
arr += unresolved_refs(subschema)
|
179
|
+
end
|
146
180
|
end
|
181
|
+
end
|
182
|
+
|
183
|
+
def traverse_schema(schema)
|
184
|
+
@store.add_uri_reference(schema.uri, schema)
|
147
185
|
|
148
186
|
schema_children(schema).each do |subschema|
|
149
|
-
if subschema.reference
|
150
|
-
dereference(subschema)
|
187
|
+
if subschema.reference && !subschema.expanded?
|
188
|
+
dereference(subschema, [])
|
189
|
+
end
|
190
|
+
|
191
|
+
# traverse child schemas only if they're the original copy
|
192
|
+
if subschema.expanded? && subschema.original?
|
193
|
+
traverse_schema(subschema)
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
# after finishing a schema traversal, find all clones and re-hydrate them
|
198
|
+
if schema.original?
|
199
|
+
schema.clones.each do |clone_schema|
|
200
|
+
parent = clone_schema.parent
|
201
|
+
clone_schema.copy_from(schema)
|
202
|
+
clone_schema.parent = parent
|
151
203
|
end
|
152
|
-
traverse_schema(subschema)
|
153
204
|
end
|
154
205
|
end
|
155
206
|
end
|