Skip to content

Commit

Permalink
Consider all siblings in unevaluated keywords
Browse files Browse the repository at this point in the history
This uses all adjacent (same schema) results to calculate unevaluated
keys/items, whether the keywords validated successfully or not.
Previously, it only considered valid results, which causes confusing
errors:

```ruby
schemer = JSONSchemer.schema({
  'properties' => {
    'x' => {
      'type' => 'integer'
    }
  },
  'unevaluatedProperties' => false
})
schemer.validate({ 'x' => 'invalid' }).map { _1.fetch_values('schema_pointer', 'error') }
 # =>
 # [["/properties/x", "value at `/x` is not an integer"],
 #  ["/unevaluatedProperties", "value at `/x` does not match schema"]]
```

The overall validation result shouldn't be affected, since the only
additional keywords that it considers are failed ones (meaning the
entire schema fails regardless of the unevaluated keys/items).
Duplicate/unhelpful error messages are reduced, though, which is the
main reason for making this change.

Generally, this interpretation doesn't align with my reading of the
spec, but there's been a lot of [discussion][0] around it and I think it
makes sense from a user experience perspective. Hopefully it will get
clarified in a future draft.

Closes: #157

Related:
- json-schema-org/json-schema-spec#1172
- https://github.com/orgs/json-schema-org/discussions/67
- https://github.com/orgs/json-schema-org/discussions/57
- https://github.com/json-schema-org/json-schema-spec/blob/2cb7c7447f9b795c9940710bf0eda966a92c937f/adr/2022-04-08-cref-for-ambiguity-and-fix-later-gh-spec-issue-1172.md

[0]: json-schema-org/json-schema-spec#1172
  • Loading branch information
davishmcclurg committed Dec 16, 2023
1 parent e947f2a commit 91c895d
Show file tree
Hide file tree
Showing 4 changed files with 364 additions and 2,352 deletions.
18 changes: 10 additions & 8 deletions lib/json_schemer/draft202012/vocab/unevaluated.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ def validate(instance, instance_location, keyword_location, context)
unevaluated_items = instance.size.times.to_set

context.adjacent_results.each_value do |adjacent_result|
collect_unevaluated_items(adjacent_result, instance_location, unevaluated_items)
collect_unevaluated_items(adjacent_result, unevaluated_items)
end

nested = unevaluated_items.map do |index|
Expand All @@ -30,8 +30,7 @@ def validate(instance, instance_location, keyword_location, context)

private

def collect_unevaluated_items(result, instance_location, unevaluated_items)
return unless result.valid && result.instance_location == instance_location
def collect_unevaluated_items(result, unevaluated_items)
case result.source
when Applicator::PrefixItems
unevaluated_items.subtract(0..result.annotation)
Expand All @@ -41,7 +40,9 @@ def collect_unevaluated_items(result, instance_location, unevaluated_items)
unevaluated_items.subtract(result.annotation)
end
result.nested&.each do |subresult|
collect_unevaluated_items(subresult, instance_location, unevaluated_items)
if subresult.valid && subresult.instance_location == result.instance_location
collect_unevaluated_items(subresult, unevaluated_items)
end
end
end
end
Expand All @@ -61,7 +62,7 @@ def validate(instance, instance_location, keyword_location, context)
evaluated_keys = Set[]

context.adjacent_results.each_value do |adjacent_result|
collect_evaluated_keys(adjacent_result, instance_location, evaluated_keys)
collect_evaluated_keys(adjacent_result, evaluated_keys)
end

evaluated = instance.reject do |key, _value|
Expand All @@ -77,14 +78,15 @@ def validate(instance, instance_location, keyword_location, context)

private

def collect_evaluated_keys(result, instance_location, evaluated_keys)
return unless result.valid && result.instance_location == instance_location
def collect_evaluated_keys(result, evaluated_keys)
case result.source
when Applicator::Properties, Applicator::PatternProperties, Applicator::AdditionalProperties, UnevaluatedProperties
evaluated_keys.merge(result.annotation)
end
result.nested&.each do |subresult|
collect_evaluated_keys(subresult, instance_location, evaluated_keys)
if subresult.valid && subresult.instance_location == result.instance_location
collect_evaluated_keys(subresult, evaluated_keys)
end
end
end
end
Expand Down

0 comments on commit 91c895d

Please sign in to comment.