brandur/json_schema

Update from `0.20.3` -> `0.20.4`+ results in infinite loop

jessieay opened this issue · 4 comments

This is very similar to the issue seen / fixed here: #112

I've written a test that reproduces what I am seeing locally:

# test/json_schema/reference_expander_test.rb

  it "is not broken" do
    sample1 = {
      "id" => "http://json-schema.org/draft-04/schema#",
      "$schema" => "http://json-schema.org/draft-04/schema#",
      "properties" => {
        "additionalItems" => {
          "anyOf" => [ { "$ref" => "#" } ]
        }
      }
    }
    schema1 = JsonSchema::Parser.new.parse!(sample1)
    sample2 = {
      "$schema" => "http://json-schema.org/draft-04/hyper-schema#",
      "id" => "http://json-schema.org/draft-04/hyper-schema#",
      "allOf" => [
        { "$ref" => "http://json-schema.org/draft-04/schema#" }
      ]
    }
    schema2 = JsonSchema::Parser.new.parse!(sample2)

    store = JsonSchema::DocumentStore.new
    expander = JsonSchema::ReferenceExpander.new

    store.add_schema(schema1)
    store.add_schema(schema2)

    expander.expand!(schema2, store: store)

    assert schema1.expanded?
    assert schema2.expanded?
  end

learned via trial and error that if I change the value of ref inside "allOf" to "#" this spec passes. So, similar to the other fix, it appears that this infinite loop is happening somewhere within `dereference.

I've got this on my todo list to debug. I have a suspicion about what is going on, but I need to actually trace through!

Hi @kytrinyx! I was wondering if you might be able to drop me a hint on what you think the issue is here? I would love to help debug but it would be very helpful to have some guidance, especially if you already have an inkling of what's going on.

So sorry @jessieay. I have no recollection of whatever my suspicion was, so I'm debugging this from scratch. My approach is to stick a binding pry in the dereference method and run only the target test, and then eyeball things until something jumps out at me.

Eyeballing this, I think that the problem is that we don't recurse into subschema's schema children in this particular case. There's a point in the dereferencing where the new_schema points to the first schema in your test, and ref is a global reference. So we start manually expanding references here:

schema_children(new_schema) do |subschema|

But the subschema has a subschema and yet when we call dereference here, it doesn't recurse into it:

dereference(subschema, ref_stack, parent_ref: ref)

So I think that what we have to do is some extra work here in the case where parent_ref is not nil, because it calls dereference on the subschema, and then we end up in the infinite loop.

schema_children(ref_schema) do |subschema|
next unless subschema.reference
if !subschema.reference.uri && parent_ref
subschema.reference = JsonReference::Reference.new("#{parent_ref.uri}#{subschema.reference.pointer}")
end
dereference(subschema, ref_stack)
end
return true

Thinking out loud...

At that spot, ref_schema.uri is the same as subschema.reference.uri.to_s.... I wonder why one is a URI object and one is a String. Whatever the case, though, I think that when they are equivalent, we need to stop dereferencing.