Skip to content

Commit

Permalink
Fix keyword refs
Browse files Browse the repository at this point in the history
This fixes two problems with JSON pointer refs and known keywords:

- If the ref resolved to a `Keyword` object with a schema value (eg,
  `#/items`), `unknown_schema!` was called, which was only defined for
  `UnknownKeyword`. I changed the name to `parsed_schema` and defined it
  for all keywords, though it still feels brittle since it requires
  keywords to pre-parse their values into schema objects.

- If the ref resolved through a `Keyword` object with a schema value
  (eg, '#/items/not`), `fetch` was called on the keyword's parsed
  schema, which wasn't defined. I settled on a `fetch` method for
  `Keyword` and `Schema` to find child objects by key/index. That way a
  keyword with a schema value will delegate `fetch` to its parsed schema
  object. `UnknownKeyword` also becomes less of a special case.

I also added a `Schema` type check after pointer resolution to help
people identify misdirected pointers.

Closes: #159
  • Loading branch information
davishmcclurg committed Nov 23, 2023
1 parent f48c563 commit 52fb83f
Show file tree
Hide file tree
Showing 4 changed files with 98 additions and 11 deletions.
6 changes: 3 additions & 3 deletions lib/json_schemer/draft202012/vocab/core.rb
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ def parse
end
end

def fetch_unknown!(token)
def fetch(token)
if value.is_a?(Hash)
parsed[token] ||= JSONSchemer::Schema::UNKNOWN_KEYWORD_CLASS.new(value.fetch(token), self, token, schema)
elsif value.is_a?(Array)
Expand All @@ -146,8 +146,8 @@ def fetch_unknown!(token)
end
end

def unknown_schema!
@unknown_schema ||= subschema(value)
def parsed_schema
@parsed_schema ||= subschema(value)
end

def validate(instance, instance_location, keyword_location, _context)
Expand Down
8 changes: 8 additions & 0 deletions lib/json_schemer/keyword.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,14 @@ def error_key
keyword
end

def fetch(key)
parsed.fetch(parsed.is_a?(Array) ? key.to_i : key)
end

def parsed_schema
parsed.is_a?(Schema) ? parsed : nil
end

private

def parse
Expand Down
15 changes: 7 additions & 8 deletions lib/json_schemer/schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -203,18 +203,13 @@ def resolve_ref(uri)
end

schema = Hana::Pointer.parse(pointer).reduce(schema) do |obj, token|
if obj.is_a?(UNKNOWN_KEYWORD_CLASS)
obj.fetch_unknown!(token)
elsif obj.parsed.is_a?(Array)
obj.parsed.fetch(token.to_i)
else
obj.parsed.fetch(token)
end
obj.fetch(token)
rescue IndexError
raise InvalidRefPointer, pointer
end

schema = schema.unknown_schema! unless schema.is_a?(Schema)
schema = schema.parsed_schema if schema.is_a?(Keyword)
raise InvalidRefPointer, pointer unless schema.is_a?(Schema)

schema
end
Expand Down Expand Up @@ -299,6 +294,10 @@ def error_key
'^'
end

def fetch(key)
parsed.fetch(key)
end

def fetch_format(format, *args, &block)
if meta_schema == self
formats.fetch(format, *args, &block)
Expand Down
80 changes: 80 additions & 0 deletions test/ref_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -395,4 +395,84 @@ def test_exclusive_ref_supports_definitions_with_id_and_json_pointer
assert(schema.valid?(1))
refute(schema.valid?('1'))
end

def test_keyword_ref
schemer = JSONSchemer.schema({
'$ref' => '#/items',
'items' => {
'type' => 'integer'
}
})
assert(schemer.valid?(1))
refute(schemer.valid?('1'))
end

def test_nested_keyword_ref
schemer = JSONSchemer.schema({
'$ref' => '#/items/not',
'items' => {
'not' => {
'type' => 'integer'
}
}
})
assert(schemer.valid?(1))
refute(schemer.valid?('1'))

schemer = JSONSchemer.schema({
'$ref' => '#/properties/a',
'properties' => {
'a' => {
'type' => 'integer'
}
}
})
assert(schemer.valid?(1))
refute(schemer.valid?('1'))
end

def test_complex_nested_keyword_ref
schemer = JSONSchemer.schema({
'$ref' => '#/definitions/a/allOf/0/items/properties/b/unknown-array/0/unknown-hash/c',
'definitions' => {
'a' => {
'allOf' => [
{
'items' => {
'properties' => {
'b' => {
'unknown-array' => [
{
'unknown-hash' => {
'c' => {
'type' => 'integer'
}
}
}
]
}
}
}
}
]
}
}
})
assert(schemer.valid?(1))
refute(schemer.valid?('1'))
end

def test_non_schema_ref_pointer
schemer = JSONSchemer.schema({
'$ref' => '#/allOf',
'allOf' => [true]
})
assert_raises(JSONSchemer::InvalidRefPointer) { schemer.valid?(1) }

schemer = JSONSchemer.schema({
'$ref' => '#/type/0',
'type' => ['integer', 'string']
})
assert_raises(JSONSchemer::InvalidRefPointer) { schemer.valid?(1) }
end
end

0 comments on commit 52fb83f

Please sign in to comment.