json_schema 0.0.7 → 0.0.9
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.
- 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
|