Skip to content

Commit 7f7b3ff

Browse files
committed
Support refs that use ids with JSON pointers
This allows a `$ref` to look up a schema by `$id` and then apply a JSON pointer to use a subschema. The test suite example schema looks like this: ```json { "$id": "urn:uuid:deadbeef-1234-0000-0000-4321feebdaed", "properties": { "foo": {"$ref": "urn:uuid:deadbeef-1234-0000-0000-4321feebdaed#/$defs/bar"} }, "$defs": { "bar": {"type": "string"} } } ``` Previously, ids were looked up using a URI that included the fragment (ie, json pointer). Now if the fragment is a valid json pointer, the schema is looked up without the fragment and the pointer is applied to whatever is found. I tried to simplify things as much as I could, but it still ended up quite complicated. A slightly separate path is still necessary for refs that start with `#` because they don't have to be encoded as URIs and aren't always [valid URIs][0]. Refs are sent to the ref resolver if they aren't found by id and aren't local json pointers. `parent_uri` is a little tricky and I'm not sure I got it totally right in all scenarios, but it's passing all the tests. Exposed by: json-schema-org/JSON-Schema-Test-Suite#578 [0]: b91115e
1 parent 520b5a6 commit 7f7b3ff

File tree

1 file changed

+29
-37
lines changed

1 file changed

+29
-37
lines changed

lib/json_schemer/schema/base.rb

+29-37
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,8 @@ def validate(data)
9191

9292
protected
9393

94+
attr_reader :root
95+
9496
def valid_instance?(instance)
9597
validate_instance(instance).none?
9698
end
@@ -229,7 +231,7 @@ def ids
229231

230232
private
231233

232-
attr_reader :root, :formats, :keywords, :ref_resolver, :regexp_resolver
234+
attr_reader :formats, :keywords, :ref_resolver, :regexp_resolver
233235

234236
def id_keyword
235237
ID_KEYWORD
@@ -307,51 +309,41 @@ def validate_type(instance, type, &block)
307309
end
308310

309311
def validate_ref(instance, ref, &block)
312+
ref_uri_pointer = nil
313+
310314
if ref.start_with?('#')
311-
schema_pointer = ref.slice(1..-1)
312-
if valid_json_pointer?(schema_pointer)
313-
ref_pointer = Hana::Pointer.new(URI.decode_www_form_component(schema_pointer))
314-
ref_root = ids[instance.parent_uri.to_s]&.fetch(:schema) || root
315-
subinstance = instance.merge(
316-
schema: ref_pointer.eval(ref_root),
317-
schema_pointer: schema_pointer,
318-
parent_uri: (pointer_uri(ref_root, ref_pointer) || instance.parent_uri)
319-
)
320-
validate_instance(subinstance, &block)
321-
return
315+
fragment = ref.slice(1..-1)
316+
if valid_json_pointer?(fragment)
317+
ref = ''
318+
ref_uri_pointer = fragment
322319
end
323320
end
324321

325322
ref_uri = join_uri(instance.parent_uri, ref)
326323

327324
if valid_json_pointer?(ref_uri.fragment)
328-
ref_pointer = Hana::Pointer.new(URI.decode_www_form_component(ref_uri.fragment))
329-
ref_root = resolve_ref(ref_uri)
330-
ref_object = child(ref_root)
331-
subinstance = instance.merge(
332-
schema: ref_pointer.eval(ref_root),
333-
schema_pointer: ref_uri.fragment,
334-
parent_uri: (pointer_uri(ref_root, ref_pointer) || ref_uri)
335-
)
336-
ref_object.validate_instance(subinstance, &block)
337-
elsif id = ids[ref_uri.to_s]
338-
subinstance = instance.merge(
339-
schema: id.fetch(:schema),
340-
schema_pointer: id.fetch(:pointer),
341-
parent_uri: ref_uri
342-
)
343-
validate_instance(subinstance, &block)
325+
ref_uri_pointer ||= ref_uri.fragment
326+
ref_uri.fragment = nil
327+
end
328+
329+
ref_pointer = Hana::Pointer.new(URI.decode_www_form_component(ref_uri_pointer || ''))
330+
331+
ref_object = if ids.key?(ref_uri.to_s) || (ref_uri_pointer && (ref_uri == instance.parent_uri || ref_uri.to_s.empty?))
332+
self
344333
else
345-
ref_root = resolve_ref(ref_uri)
346-
ref_object = child(ref_root)
347-
id = ref_object.ids[ref_uri.to_s] || { schema: ref_root, pointer: '' }
348-
subinstance = instance.merge(
349-
schema: id.fetch(:schema),
350-
schema_pointer: id.fetch(:pointer),
351-
parent_uri: ref_uri
352-
)
353-
ref_object.validate_instance(subinstance, &block)
334+
child(resolve_ref(ref_uri))
354335
end
336+
337+
ref_schema, ref_schema_pointer = ref_object.ids[ref_uri.to_s]&.fetch_values(:schema, :pointer) || [ref_object.root, '']
338+
parent_uri = ref_uri_pointer ? (pointer_uri(ref_schema, ref_pointer) || instance.parent_uri) : ref_uri
339+
340+
subinstance = instance.merge(
341+
schema: ref_pointer.eval(ref_schema),
342+
schema_pointer: "#{ref_schema_pointer}#{ref_uri_pointer}",
343+
parent_uri: parent_uri
344+
)
345+
346+
ref_object.validate_instance(subinstance, &block)
355347
end
356348

357349
def validate_custom_format(instance, custom_format)

0 commit comments

Comments
 (0)