jsi 0.7.0 → 0.8.0
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/.yardopts +6 -1
- data/CHANGELOG.md +15 -0
- data/README.md +19 -18
- data/jsi.gemspec +2 -3
- data/lib/jsi/base/mutability.rb +44 -0
- data/lib/jsi/base/node.rb +199 -34
- data/lib/jsi/base.rb +412 -228
- data/lib/jsi/jsi_coder.rb +18 -16
- data/lib/jsi/metaschema_node/bootstrap_schema.rb +57 -23
- data/lib/jsi/metaschema_node.rb +138 -107
- data/lib/jsi/ptr.rb +59 -37
- data/lib/jsi/schema/application/child_application/draft04.rb +0 -1
- data/lib/jsi/schema/application/child_application/draft06.rb +0 -1
- data/lib/jsi/schema/application/child_application/draft07.rb +0 -1
- data/lib/jsi/schema/application/child_application.rb +0 -25
- data/lib/jsi/schema/application/inplace_application/draft04.rb +0 -1
- data/lib/jsi/schema/application/inplace_application/draft06.rb +0 -1
- data/lib/jsi/schema/application/inplace_application/draft07.rb +0 -1
- data/lib/jsi/schema/application/inplace_application/ref.rb +1 -1
- data/lib/jsi/schema/application/inplace_application/someof.rb +1 -1
- data/lib/jsi/schema/application/inplace_application.rb +0 -27
- data/lib/jsi/schema/draft04.rb +0 -1
- data/lib/jsi/schema/draft06.rb +0 -1
- data/lib/jsi/schema/draft07.rb +0 -1
- data/lib/jsi/schema/ref.rb +44 -18
- data/lib/jsi/schema/schema_ancestor_node.rb +65 -56
- data/lib/jsi/schema/validation/contains.rb +1 -1
- data/lib/jsi/schema/validation/draft04/minmax.rb +2 -0
- data/lib/jsi/schema/validation/draft04.rb +0 -2
- data/lib/jsi/schema/validation/draft06.rb +0 -2
- data/lib/jsi/schema/validation/draft07.rb +0 -2
- data/lib/jsi/schema/validation/items.rb +3 -3
- data/lib/jsi/schema/validation/pattern.rb +1 -1
- data/lib/jsi/schema/validation/properties.rb +4 -4
- data/lib/jsi/schema/validation/ref.rb +1 -1
- data/lib/jsi/schema/validation.rb +0 -2
- data/lib/jsi/schema.rb +405 -194
- data/lib/jsi/schema_classes.rb +196 -127
- data/lib/jsi/schema_registry.rb +66 -17
- data/lib/jsi/schema_set.rb +76 -30
- data/lib/jsi/simple_wrap.rb +2 -7
- data/lib/jsi/util/private/attr_struct.rb +28 -14
- data/lib/jsi/util/private/memo_map.rb +75 -0
- data/lib/jsi/util/private.rb +73 -92
- data/lib/jsi/util/typelike.rb +28 -28
- data/lib/jsi/util.rb +120 -36
- data/lib/jsi/validation/error.rb +4 -0
- data/lib/jsi/validation/result.rb +18 -32
- data/lib/jsi/version.rb +1 -1
- data/lib/jsi.rb +67 -25
- data/lib/schemas/json-schema.org/draft-04/schema.rb +159 -4
- data/lib/schemas/json-schema.org/draft-06/schema.rb +161 -4
- data/lib/schemas/json-schema.org/draft-07/schema.rb +188 -4
- metadata +19 -5
- data/lib/jsi/metaschema.rb +0 -6
- data/lib/jsi/schema/validation/core.rb +0 -39
data/lib/jsi/ptr.rb
CHANGED
@@ -16,12 +16,17 @@ module JSI
|
|
16
16
|
class ResolutionError < Error
|
17
17
|
end
|
18
18
|
|
19
|
+
POS_INT_RE = /\A[1-9]\d*\z/
|
20
|
+
private_constant :POS_INT_RE
|
21
|
+
|
19
22
|
# instantiates a pointer or returns the given pointer
|
20
23
|
# @param ary_ptr [#to_ary, JSI::Ptr] an array of tokens, or a pointer
|
21
24
|
# @return [JSI::Ptr]
|
22
25
|
def self.ary_ptr(ary_ptr)
|
23
26
|
if ary_ptr.is_a?(Ptr)
|
24
27
|
ary_ptr
|
28
|
+
elsif ary_ptr == Util::EMPTY_ARY
|
29
|
+
EMPTY
|
25
30
|
else
|
26
31
|
new(ary_ptr)
|
27
32
|
end
|
@@ -42,7 +47,7 @@ module JSI
|
|
42
47
|
# @param tokens any number of tokens
|
43
48
|
# @return [JSI::Ptr]
|
44
49
|
def self.[](*tokens)
|
45
|
-
new(tokens)
|
50
|
+
tokens.empty? ? EMPTY : new(tokens.freeze)
|
46
51
|
end
|
47
52
|
|
48
53
|
# parse a URI-escaped fragment and instantiate as a JSI::Ptr
|
@@ -55,6 +60,12 @@ module JSI
|
|
55
60
|
# JSI::Ptr.from_fragment('/foo%20bar')
|
56
61
|
# => JSI::Ptr["foo bar"]
|
57
62
|
#
|
63
|
+
# Note: A fragment does not include a leading '#'. The string "#/foo" is a URI containing the
|
64
|
+
# fragment "/foo", which should be parsed by `Addressable::URI` before passing to this method, e.g.:
|
65
|
+
#
|
66
|
+
# JSI::Ptr.from_fragment(Addressable::URI.parse("#/foo").fragment)
|
67
|
+
# => JSI::Ptr["foo"]
|
68
|
+
#
|
58
69
|
# @param fragment [String] a fragment containing a pointer
|
59
70
|
# @return [JSI::Ptr]
|
60
71
|
# @raise [JSI::Ptr::PointerSyntaxError] when the fragment does not contain a pointer with
|
@@ -75,13 +86,17 @@ module JSI
|
|
75
86
|
# @return [JSI::Ptr]
|
76
87
|
# @raise [JSI::Ptr::PointerSyntaxError] when the pointer_string does not have valid pointer syntax
|
77
88
|
def self.from_pointer(pointer_string)
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
89
|
+
pointer_string = pointer_string.to_str
|
90
|
+
if pointer_string[0] == ?/
|
91
|
+
tokens = pointer_string.split('/', -1).map! do |piece|
|
92
|
+
piece.gsub!('~1', '/')
|
93
|
+
piece.gsub!('~0', '~')
|
94
|
+
piece.freeze
|
95
|
+
end
|
96
|
+
tokens.shift
|
97
|
+
new(tokens.freeze)
|
98
|
+
elsif pointer_string.empty?
|
99
|
+
EMPTY
|
85
100
|
else
|
86
101
|
raise(PointerSyntaxError, "Invalid pointer syntax in #{pointer_string.inspect}: pointer must begin with /")
|
87
102
|
end
|
@@ -94,7 +109,7 @@ module JSI
|
|
94
109
|
unless tokens.respond_to?(:to_ary)
|
95
110
|
raise(TypeError, "tokens must be an array. got: #{tokens.inspect}")
|
96
111
|
end
|
97
|
-
@tokens = tokens.to_ary
|
112
|
+
@tokens = Util.deep_to_frozen(tokens.to_ary, not_implemented: proc { |o| o })
|
98
113
|
end
|
99
114
|
|
100
115
|
attr_reader :tokens
|
@@ -117,19 +132,19 @@ module JSI
|
|
117
132
|
# the pointer string representation of this pointer
|
118
133
|
# @return [String]
|
119
134
|
def pointer
|
120
|
-
tokens.map { |t| '/' + t.to_s.gsub('~', '~0').gsub('/', '~1') }.join('')
|
135
|
+
tokens.map { |t| '/' + t.to_s.gsub('~', '~0').gsub('/', '~1') }.join('').freeze
|
121
136
|
end
|
122
137
|
|
123
138
|
# the fragment string representation of this pointer
|
124
139
|
# @return [String]
|
125
140
|
def fragment
|
126
|
-
Addressable::URI.escape(pointer)
|
141
|
+
Addressable::URI.escape(pointer).freeze
|
127
142
|
end
|
128
143
|
|
129
144
|
# a URI consisting of a fragment containing this pointer's fragment string representation
|
130
145
|
# @return [Addressable::URI]
|
131
146
|
def uri
|
132
|
-
Addressable::URI.new(fragment: fragment)
|
147
|
+
Addressable::URI.new(fragment: fragment).freeze
|
133
148
|
end
|
134
149
|
|
135
150
|
# whether this pointer is empty, i.e. it has no tokens
|
@@ -149,29 +164,33 @@ module JSI
|
|
149
164
|
if root?
|
150
165
|
raise(Ptr::Error, "cannot access parent of root pointer: #{pretty_inspect.chomp}")
|
151
166
|
end
|
152
|
-
Ptr.new(tokens[0...-1])
|
167
|
+
tokens.size == 1 ? EMPTY : Ptr.new(tokens[0...-1].freeze)
|
153
168
|
end
|
154
169
|
|
155
|
-
# whether this pointer
|
156
|
-
#
|
170
|
+
# whether this pointer is an ancestor of `other_ptr`, a descendent pointer.
|
171
|
+
# `ancestor_of?` is inclusive; a pointer is an ancestor of itself.
|
172
|
+
#
|
157
173
|
# @return [Boolean]
|
158
|
-
def
|
174
|
+
def ancestor_of?(other_ptr)
|
159
175
|
tokens == other_ptr.tokens[0...tokens.size]
|
160
176
|
end
|
161
177
|
|
178
|
+
# @deprecated
|
179
|
+
def contains?(other_ptr)
|
180
|
+
ancestor_of?(other_ptr)
|
181
|
+
end
|
182
|
+
|
162
183
|
# part of this pointer relative to the given ancestor_ptr
|
163
184
|
# @return [JSI::Ptr]
|
164
185
|
# @raise [JSI::Ptr::Error] if the given ancestor_ptr is not an ancestor of this pointer
|
165
186
|
def relative_to(ancestor_ptr)
|
166
|
-
|
187
|
+
return self if ancestor_ptr.empty?
|
188
|
+
unless ancestor_ptr.ancestor_of?(self)
|
167
189
|
raise(Error, "ancestor_ptr #{ancestor_ptr.inspect} is not ancestor of #{inspect}")
|
168
190
|
end
|
169
|
-
Ptr.new(tokens[ancestor_ptr.tokens.size..-1])
|
191
|
+
ancestor_ptr.tokens.size == tokens.size ? EMPTY : Ptr.new(tokens[ancestor_ptr.tokens.size..-1].freeze)
|
170
192
|
end
|
171
193
|
|
172
|
-
# @deprecated after v0.6
|
173
|
-
alias_method :ptr_relative_to, :relative_to
|
174
|
-
|
175
194
|
# a pointer with the tokens of this one plus the given `ptr`'s.
|
176
195
|
# @param ptr [JSI::Ptr, #to_ary]
|
177
196
|
# @return [JSI::Ptr]
|
@@ -183,7 +202,7 @@ module JSI
|
|
183
202
|
else
|
184
203
|
raise(TypeError, "ptr must be a #{Ptr} or Array of tokens; got: #{ptr.inspect}")
|
185
204
|
end
|
186
|
-
Ptr.new(tokens + ptr_tokens)
|
205
|
+
ptr_tokens.empty? ? self : Ptr.new((tokens + ptr_tokens).freeze)
|
187
206
|
end
|
188
207
|
|
189
208
|
# a pointer consisting of the first `n` of our tokens
|
@@ -191,10 +210,10 @@ module JSI
|
|
191
210
|
# @return [JSI::Ptr]
|
192
211
|
# @raise [ArgumentError] if n is not between 0 and the size of our tokens
|
193
212
|
def take(n)
|
194
|
-
unless (0
|
213
|
+
unless n.is_a?(Integer) && n >= 0 && n <= tokens.size
|
195
214
|
raise(ArgumentError, "n not in range (0..#{tokens.size}): #{n.inspect}")
|
196
215
|
end
|
197
|
-
Ptr.new(tokens.take(n))
|
216
|
+
n == tokens.size ? self : Ptr.new(tokens.take(n).freeze)
|
198
217
|
end
|
199
218
|
|
200
219
|
# appends the given token to this pointer's tokens and returns the result
|
@@ -202,7 +221,7 @@ module JSI
|
|
202
221
|
# @param token [Object]
|
203
222
|
# @return [JSI::Ptr] pointer to a child node of this pointer with the given token
|
204
223
|
def [](token)
|
205
|
-
Ptr.new(tokens
|
224
|
+
Ptr.new(tokens.dup.push(token).freeze)
|
206
225
|
end
|
207
226
|
|
208
227
|
# takes a document and a block. the block is yielded the content of the given document at this
|
@@ -227,7 +246,7 @@ module JSI
|
|
227
246
|
Util.modified_copy(document, &block)
|
228
247
|
else
|
229
248
|
car = tokens[0]
|
230
|
-
cdr = Ptr.new(tokens[1..-1])
|
249
|
+
cdr = tokens.size == 1 ? EMPTY : Ptr.new(tokens[1..-1].freeze)
|
231
250
|
token, document_child = node_subscript_token_child(document, car)
|
232
251
|
modified_document_child = cdr.modified_document_copy(document_child, &block)
|
233
252
|
if modified_document_child.object_id == document_child.object_id
|
@@ -246,33 +265,36 @@ module JSI
|
|
246
265
|
# a string representation of this pointer
|
247
266
|
# @return [String]
|
248
267
|
def inspect
|
249
|
-
"#{self.class.name}[#{tokens.map(&:inspect).join(", ")}]"
|
268
|
+
-"#{self.class.name}[#{tokens.map(&:inspect).join(", ")}]"
|
250
269
|
end
|
251
270
|
|
252
|
-
|
271
|
+
def to_s
|
272
|
+
inspect
|
273
|
+
end
|
253
274
|
|
254
|
-
#
|
275
|
+
# see {Util::Private::FingerprintHash}
|
276
|
+
# @api private
|
255
277
|
def jsi_fingerprint
|
256
|
-
{class: Ptr, tokens: tokens}
|
278
|
+
{class: Ptr, tokens: tokens}.freeze
|
257
279
|
end
|
258
|
-
include Util::FingerprintHash
|
280
|
+
include Util::FingerprintHash::Immutable
|
281
|
+
|
282
|
+
EMPTY = new(Util::EMPTY_ARY)
|
259
283
|
|
260
284
|
private
|
261
285
|
|
262
286
|
def node_subscript_token_child(value, token, *a, **kw)
|
263
287
|
if value.respond_to?(:to_ary)
|
264
|
-
if token.is_a?(String) && token =~
|
288
|
+
if token.is_a?(String) && (token == '0' || token =~ POS_INT_RE)
|
265
289
|
token = token.to_i
|
266
290
|
elsif token == '-'
|
267
291
|
# per rfc6901, - refers "to the (nonexistent) member after the last array element" and is
|
268
292
|
# expected to raise an error condition.
|
269
293
|
raise(ResolutionError, "Invalid resolution: #{token.inspect} refers to a nonexistent element in array #{value.inspect}")
|
270
294
|
end
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
unless (0...(value.respond_to?(:size) ? value : value.to_ary).size).include?(token)
|
275
|
-
raise(ResolutionError, "Invalid resolution: #{token.inspect} is not a valid index of #{value.inspect}")
|
295
|
+
size = (value.respond_to?(:size) ? value : value.to_ary).size
|
296
|
+
unless token.is_a?(Integer) && token >= 0 && token < size
|
297
|
+
raise(ResolutionError, "Invalid resolution: #{token.inspect} is not a valid array index of #{value.inspect}")
|
276
298
|
end
|
277
299
|
|
278
300
|
ary = (value.respond_to?(:[]) ? value : value.to_ary)
|
@@ -2,7 +2,6 @@
|
|
2
2
|
|
3
3
|
module JSI
|
4
4
|
module Schema::Application::ChildApplication::Draft06
|
5
|
-
include Schema::Application::ChildApplication
|
6
5
|
include Schema::Application::ChildApplication::Items
|
7
6
|
include Schema::Application::ChildApplication::Contains
|
8
7
|
include Schema::Application::ChildApplication::Properties
|
@@ -2,7 +2,6 @@
|
|
2
2
|
|
3
3
|
module JSI
|
4
4
|
module Schema::Application::ChildApplication::Draft07
|
5
|
-
include Schema::Application::ChildApplication
|
6
5
|
include Schema::Application::ChildApplication::Items
|
7
6
|
include Schema::Application::ChildApplication::Contains
|
8
7
|
include Schema::Application::ChildApplication::Properties
|
@@ -9,30 +9,5 @@ module JSI
|
|
9
9
|
autoload :Items, 'jsi/schema/application/child_application/items'
|
10
10
|
autoload :Contains, 'jsi/schema/application/child_application/contains'
|
11
11
|
autoload :Properties, 'jsi/schema/application/child_application/properties'
|
12
|
-
|
13
|
-
# a set of child applicator subschemas of this schema which apply to the child of the given instance
|
14
|
-
# on the given token.
|
15
|
-
#
|
16
|
-
# @param token [Object] the array index or object property name for the child instance
|
17
|
-
# @param instance [Object] the instance to check any child applicators against
|
18
|
-
# @return [JSI::SchemaSet] child applicator subschemas of this schema for the given token
|
19
|
-
# of the instance
|
20
|
-
def child_applicator_schemas(token, instance)
|
21
|
-
SchemaSet.new(each_child_applicator_schema(token, instance))
|
22
|
-
end
|
23
|
-
|
24
|
-
# yields each child applicator subschema (from properties, items, etc.) which applies to the child of
|
25
|
-
# the given instance on the given token.
|
26
|
-
#
|
27
|
-
# @param (see #child_applicator_schemas)
|
28
|
-
# @yield [JSI::Schema]
|
29
|
-
# @return [nil, Enumerator] an Enumerator if invoked without a block; otherwise nil
|
30
|
-
def each_child_applicator_schema(token, instance, &block)
|
31
|
-
return to_enum(__method__, token, instance) unless block
|
32
|
-
|
33
|
-
internal_child_applicate_keywords(token, instance, &block)
|
34
|
-
|
35
|
-
nil
|
36
|
-
end
|
37
12
|
end
|
38
13
|
end
|
@@ -2,7 +2,6 @@
|
|
2
2
|
|
3
3
|
module JSI
|
4
4
|
module Schema::Application::InplaceApplication::Draft04
|
5
|
-
include Schema::Application::InplaceApplication
|
6
5
|
include Schema::Application::InplaceApplication::Ref
|
7
6
|
include Schema::Application::InplaceApplication::Dependencies
|
8
7
|
include Schema::Application::InplaceApplication::SomeOf
|
@@ -2,7 +2,6 @@
|
|
2
2
|
|
3
3
|
module JSI
|
4
4
|
module Schema::Application::InplaceApplication::Draft06
|
5
|
-
include Schema::Application::InplaceApplication
|
6
5
|
include Schema::Application::InplaceApplication::Ref
|
7
6
|
include Schema::Application::InplaceApplication::Dependencies
|
8
7
|
include Schema::Application::InplaceApplication::SomeOf
|
@@ -2,7 +2,6 @@
|
|
2
2
|
|
3
3
|
module JSI
|
4
4
|
module Schema::Application::InplaceApplication::Draft07
|
5
|
-
include Schema::Application::InplaceApplication
|
6
5
|
include Schema::Application::InplaceApplication::Ref
|
7
6
|
include Schema::Application::InplaceApplication::Dependencies
|
8
7
|
include Schema::Application::InplaceApplication::IfThenElse
|
@@ -5,7 +5,7 @@ module JSI
|
|
5
5
|
# @private
|
6
6
|
def internal_applicate_ref(instance, visited_refs, throw_done: false, &block)
|
7
7
|
if keyword?('$ref') && schema_content['$ref'].respond_to?(:to_str)
|
8
|
-
ref =
|
8
|
+
ref = schema_ref('$ref')
|
9
9
|
unless visited_refs.include?(ref)
|
10
10
|
ref.deref_schema.each_inplace_applicator_schema(instance, visited_refs: visited_refs + [ref], &block)
|
11
11
|
if throw_done
|
@@ -27,7 +27,7 @@ module JSI
|
|
27
27
|
oneOf_idxs = schema_content['oneOf'].each_index
|
28
28
|
subschema_idx_valid = Hash.new { |h, i| h[i] = subschema(['oneOf', i]).instance_valid?(instance) }
|
29
29
|
# count up to 2 `oneOf` subschemas which `instance` validates against
|
30
|
-
nvalid = oneOf_idxs.inject(0) { |n, i| n
|
30
|
+
nvalid = oneOf_idxs.inject(0) { |n, i| n <= 1 && subschema_idx_valid[i] ? n + 1 : n }
|
31
31
|
if nvalid == 1
|
32
32
|
applicator_idxs = oneOf_idxs.select { |i| subschema_idx_valid[i] }
|
33
33
|
else
|
@@ -10,32 +10,5 @@ module JSI
|
|
10
10
|
autoload :SomeOf, 'jsi/schema/application/inplace_application/someof'
|
11
11
|
autoload :IfThenElse, 'jsi/schema/application/inplace_application/ifthenelse'
|
12
12
|
autoload :Dependencies, 'jsi/schema/application/inplace_application/dependencies'
|
13
|
-
|
14
|
-
# a set of inplace applicator schemas of this schema (from $ref, allOf, etc.) which apply to the
|
15
|
-
# given instance.
|
16
|
-
#
|
17
|
-
# the returned set will contain this schema itself, unless this schema contains a $ref keyword.
|
18
|
-
#
|
19
|
-
# @param instance [Object] the instance to check any applicators against
|
20
|
-
# @return [JSI::SchemaSet] matched applicator schemas
|
21
|
-
def inplace_applicator_schemas(instance)
|
22
|
-
SchemaSet.new(each_inplace_applicator_schema(instance))
|
23
|
-
end
|
24
|
-
|
25
|
-
# yields each inplace applicator schema which applies to the given instance.
|
26
|
-
#
|
27
|
-
# @param instance (see #inplace_applicator_schemas)
|
28
|
-
# @param visited_refs [Enumerable<JSI::Schema::Ref>]
|
29
|
-
# @yield [JSI::Schema]
|
30
|
-
# @return [nil, Enumerator] an Enumerator if invoked without a block; otherwise nil
|
31
|
-
def each_inplace_applicator_schema(instance, visited_refs: [], &block)
|
32
|
-
return to_enum(__method__, instance, visited_refs: visited_refs) unless block
|
33
|
-
|
34
|
-
catch(:jsi_application_done) do
|
35
|
-
internal_inplace_applicate_keywords(instance, visited_refs, &block)
|
36
|
-
end
|
37
|
-
|
38
|
-
nil
|
39
|
-
end
|
40
13
|
end
|
41
14
|
end
|
data/lib/jsi/schema/draft04.rb
CHANGED
data/lib/jsi/schema/draft06.rb
CHANGED
data/lib/jsi/schema/draft07.rb
CHANGED
data/lib/jsi/schema/ref.rb
CHANGED
@@ -1,30 +1,45 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module JSI
|
4
|
-
# JSI::Schema::Ref is a reference to
|
5
|
-
#
|
4
|
+
# A JSI::Schema::Ref is a reference to a schema identified by a URI, typically from
|
5
|
+
# a `$ref` keyword of a schema.
|
6
6
|
class Schema::Ref
|
7
|
-
# @param ref [String]
|
8
|
-
# @param ref_schema [JSI::Schema]
|
9
|
-
|
7
|
+
# @param ref [String] A reference URI - typically the `$ref` value of the ref_schema
|
8
|
+
# @param ref_schema [JSI::Schema] A schema from which the reference originated.
|
9
|
+
#
|
10
|
+
# If the ref URI consists of only a fragment, it is resolved from the `ref_schema`'s
|
11
|
+
# {Schema#schema_resource_root}. Otherwise the resource is found in the `ref_schema`'s
|
12
|
+
# {SchemaAncestorNode#jsi_schema_registry #jsi_schema_registry} (and any fragment is resolved from there).
|
13
|
+
# @param schema_registry [SchemaRegistry] The registry in which the resource this ref refers to will be found.
|
14
|
+
# This should only be specified in the absence of a `ref_schema`.
|
15
|
+
# If neither is specified, {JSI.schema_registry} is used.
|
16
|
+
def initialize(ref, ref_schema: nil, schema_registry: nil)
|
10
17
|
raise(ArgumentError, "ref is not a string") unless ref.respond_to?(:to_str)
|
11
18
|
@ref = ref
|
12
|
-
@ref_uri =
|
19
|
+
@ref_uri = Util.uri(ref)
|
13
20
|
@ref_schema = ref_schema ? Schema.ensure_schema(ref_schema) : nil
|
21
|
+
@schema_registry = schema_registry || (ref_schema ? ref_schema.jsi_schema_registry : JSI.schema_registry)
|
22
|
+
@deref_schema = nil
|
14
23
|
end
|
15
24
|
|
25
|
+
# @return [String]
|
16
26
|
attr_reader :ref
|
17
27
|
|
28
|
+
# @return [Addressable::URI]
|
18
29
|
attr_reader :ref_uri
|
19
30
|
|
31
|
+
# @return [Schema, nil]
|
20
32
|
attr_reader :ref_schema
|
21
33
|
|
34
|
+
# @return [SchemaRegistry, nil]
|
35
|
+
attr_reader(:schema_registry)
|
36
|
+
|
22
37
|
# finds the schema this ref points to
|
23
38
|
# @return [JSI::Schema]
|
24
39
|
# @raise [JSI::Schema::NotASchemaError] when the thing this ref points to is not a schema
|
25
40
|
# @raise [JSI::Schema::ReferenceError] when this reference cannot be resolved
|
26
41
|
def deref_schema
|
27
|
-
return @deref_schema if
|
42
|
+
return @deref_schema if @deref_schema
|
28
43
|
|
29
44
|
schema_resource_root = nil
|
30
45
|
check_schema_resource_root = -> {
|
@@ -36,7 +51,7 @@ module JSI
|
|
36
51
|
end
|
37
52
|
}
|
38
53
|
|
39
|
-
ref_uri_nofrag = ref_uri.merge(fragment: nil)
|
54
|
+
ref_uri_nofrag = ref_uri.merge(fragment: nil).freeze
|
40
55
|
|
41
56
|
if ref_uri_nofrag.empty?
|
42
57
|
unless ref_schema
|
@@ -49,7 +64,7 @@ module JSI
|
|
49
64
|
# the URI only consists of a fragment (or is empty).
|
50
65
|
# for a fragment pointer, resolve using Schema#resource_root_subschema on the ref_schema.
|
51
66
|
# for a fragment anchor, bootstrap does not support anchors; otherwise use the ref_schema's schema_resource_root.
|
52
|
-
schema_resource_root = ref_schema.is_a?(
|
67
|
+
schema_resource_root = ref_schema.is_a?(MetaSchemaNode::BootstrapSchema) ? nil : ref_schema.schema_resource_root
|
53
68
|
resolve_fragment_ptr = ref_schema.method(:resource_root_subschema)
|
54
69
|
else
|
55
70
|
# find the schema_resource_root from the non-fragment URI. we will resolve any fragment, either pointer or anchor, from there.
|
@@ -57,19 +72,26 @@ module JSI
|
|
57
72
|
if ref_uri_nofrag.absolute?
|
58
73
|
ref_abs_uri = ref_uri_nofrag
|
59
74
|
elsif ref_schema && ref_schema.jsi_resource_ancestor_uri
|
60
|
-
ref_abs_uri = ref_schema.jsi_resource_ancestor_uri.join(ref_uri_nofrag)
|
75
|
+
ref_abs_uri = ref_schema.jsi_resource_ancestor_uri.join(ref_uri_nofrag).freeze
|
61
76
|
else
|
62
77
|
ref_abs_uri = nil
|
63
78
|
end
|
64
79
|
if ref_abs_uri
|
65
|
-
|
80
|
+
unless schema_registry
|
81
|
+
raise(Schema::ReferenceError, [
|
82
|
+
"could not resolve remote ref with no schema_registry specified",
|
83
|
+
"ref URI: #{ref_uri.to_s}",
|
84
|
+
("from: #{ref_schema.pretty_inspect.chomp}" if ref_schema),
|
85
|
+
].compact.join("\n"))
|
86
|
+
end
|
87
|
+
schema_resource_root = schema_registry.find(ref_abs_uri)
|
66
88
|
end
|
67
89
|
|
68
90
|
unless schema_resource_root
|
69
91
|
# HAX for how google does refs and ids
|
70
92
|
if ref_schema && ref_schema.jsi_document.respond_to?(:to_hash) && ref_schema.jsi_document['schemas'].respond_to?(:to_hash)
|
71
|
-
ref_schema.jsi_document['schemas'].
|
72
|
-
if Addressable::URI.parse(
|
93
|
+
ref_schema.jsi_document['schemas'].each do |k, v|
|
94
|
+
if Addressable::URI.parse(v['id']) == ref_uri_nofrag
|
73
95
|
schema_resource_root = ref_schema.resource_root_subschema(['schemas', k])
|
74
96
|
end
|
75
97
|
end
|
@@ -136,10 +158,12 @@ module JSI
|
|
136
158
|
|
137
159
|
# @return [String]
|
138
160
|
def inspect
|
139
|
-
|
161
|
+
-%Q(\#<#{self.class.name} #{ref}>)
|
140
162
|
end
|
141
163
|
|
142
|
-
|
164
|
+
def to_s
|
165
|
+
inspect
|
166
|
+
end
|
143
167
|
|
144
168
|
# pretty-prints a representation of self to the given printer
|
145
169
|
# @return [void]
|
@@ -151,10 +175,12 @@ module JSI
|
|
151
175
|
q.text '>'
|
152
176
|
end
|
153
177
|
|
154
|
-
#
|
178
|
+
# see {Util::Private::FingerprintHash}
|
179
|
+
# @api private
|
155
180
|
def jsi_fingerprint
|
156
|
-
{class: self.class, ref: ref, ref_schema: ref_schema}
|
181
|
+
{class: self.class, ref: ref, ref_schema: ref_schema}.freeze
|
157
182
|
end
|
158
|
-
|
183
|
+
|
184
|
+
include(Util::FingerprintHash::Immutable)
|
159
185
|
end
|
160
186
|
end
|