Skip to content

Narrow unions through $ref and allOf - #419

Open
holodorum wants to merge 1 commit into
kson-org:mainfrom
holodorum:fix/union-narrowing-through-allof
Open

Narrow unions through $ref and allOf#419
holodorum wants to merge 1 commit into
kson-org:mainfrom
holodorum:fix/union-narrowing-through-allof

Conversation

@holodorum

Copy link
Copy Markdown
Collaborator

Union narrowing picks which oneOf/anyOf branch a failing document meant, so we report that branch's error instead of every branch's. To do that it needs to know what each branch declares, and it could only see declarations written directly on the branch, or one $ref away. It could not see declarations reached through an allOf, which is where a base type's shared fields normally live:

allOf: [{$ref: Base}, {oneOf: [...]}]

The properties live on Base and the union reaches the branch through a $ref, so the branch looks empty. Nothing eliminates it, nothing matches it, and we dump every branch.

Before (two-branch union, document with a bad mode):

Value must match at least one sub-schema
Value matches none of these sub-schemas:
- Group:
    - Additional property 'mode' is not allowed (1:1)
    - Additional property 'action' is not allowed (2:1)
    - Missing required properties: members (1:1)
- Item:
    - Value must be one of: "read", "write" (1:8)

After:

Value must be one of: "read", "write"

Now we follow both composing edges, a lone $ref and allOf membership, transitively with a visited set, and read everything reached as one declaration. They all constrain the same document, so a property pinned by several of them is pinned to the intersection of their sets. Property schemas are still not followed, so child: {$ref: node} stays out of the walk.

The rest of the diff is tidy-up with no behaviour change, reviewable separately: the terms this code uses (pin, known property, discriminator) get one glossary at the top of UnionNarrowing.kt, and narrowByElimination, which also did presence matching, is split in three. Todo left on pinnedValues, which only sits on the validator interface because a schema keeps its compiled validators and not the declarations behind them.

Four tests in UnionNarrowingThroughAllOfTest. Each fails without the traversal.

Union narrowing only read a branch's own properties and required, plus
one hop through a lone $ref. That misses what code generators emit for
a base type with variants:

    allOf: [{$ref: Base}, {oneOf: [...]}]

The properties live on Base, and the union reaches the branch through a
$ref, so the branch looks empty. Nothing eliminates it, nothing matches
it, and we fall back to dumping every branch's errors.

Follow both edges instead, transitively, with a visited set, and read
everything reached as one declaration. They all constrain the same
document, so a property pinned by several of them is pinned to the
intersection of their sets. Property schemas are still not followed, so
child: {$ref: node} stays out of the walk.

Tidied up while in here. The terms this code uses (pin, known property,
discriminator) now have one glossary at the top of UnionNarrowing.kt
rather than a definition in whichever KDoc mentioned them first, and
narrowByElimination, which also did presence matching, is split in
three. Left a todo on pinnedValues: it only exists because a schema
keeps its compiled validators and not the declarations behind them.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant