Skip to content

Fix nullable allOf ref type check - #1279

Merged
stevehu merged 3 commits into
networknt:masterfrom
wiremock-inc:fix/nullable-allof-ref-type-check
Aug 27, 2026
Merged

Fix nullable allOf ref type check#1279
stevehu merged 3 commits into
networknt:masterfrom
wiremock-inc:fix/nullable-allof-ref-type-check

Conversation

@tomakehurst

Copy link
Copy Markdown
Contributor

Fixes issue where a nullable (OpenAPI dialect) on an element containing allOf isn't honoured when a null value is passed.

Closes #1278

tomakehurst and others added 2 commits August 27, 2026 12:46
A property declared nullable: true and composed via allOf containing
only a $ref (e.g. OpenAPI 3.0 style composition) incorrectly rejected
null values. The schema owning the failing type keyword is resolved
through $ref and is a cached object whose lexical parent reflects
where it is declared in the document, not where it was referenced
from, so the existing one-hop parent/grandparent nullable check never
found the nullable declaration on the referencing schema.

Walk the dynamic evaluation stack instead, following through any
chain of $ref schemas, to find the referencing schema and check it
(and its lexical parent) for nullable: true.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Confirms, per maintainer feedback on the upstream issue, that the
dynamic evaluation stack walk added for nullable allOf + $ref
compositions has no effect outside dialects that enable the nullable
keyword (currently only OpenAPI 3.0.x) — null is still rejected for
the same schema shape under draft-07.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes OpenAPI 3.0 nullable: true handling when nullable is applied to a schema composed via allOf that only contains a $ref, ensuring null values are accepted as intended (per issue #1278).

Changes:

  • Update nullable type-check logic to detect nullable: true across $ref evaluation context (not just lexical parentage).
  • Add an OpenAPI 3.0 regression test covering nullable + allOf + $ref.
  • Add a Draft-07 test asserting nullable remains ignored outside nullable-enabled dialects.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated no comments.

File Description
src/main/java/com/networknt/schema/utils/JsonNodeTypes.java Enhances nullable detection for null type mismatches by walking the evaluation schema stack to account for $ref caching/parentage.
src/test/java/com/networknt/schema/oas/OpenApi30Test.java Adds regression test reproducing issue #1278 (nullable on allOf with $ref should allow null).
src/test/java/com/networknt/schema/TypeValidatorTest.java Adds a Draft-07 test confirming nullable is not honored when the dialect does not enable the nullable keyword.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@stevehu

stevehu commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

PR review: Fix nullable allOf ref type check

Risk: HIGH — request changes

Finding

  1. [High] The change incorrectly honors nullable beside a direct $ref.

    In isNullableAncestor
    (

    private static boolean isNullableAncestor(Schema schema, ExecutionContext executionContext) {
    Schema current = schema;
    while (current != null) {
    if (isNodeNullable(current.getSchemaNode())) {
    return true;
    }
    Schema parentSchema = current.getParentSchema();
    if (parentSchema != null && isNodeNullable(parentSchema.getSchemaNode())) {
    return true;
    }
    current = findReferencingSchema(current, executionContext);
    }
    return false;
    }
    /**
    * Finds the schema that referenced the given schema through {@code $ref}, if
    * any, by looking at the schema evaluated immediately before it on the
    * dynamic evaluation stack.
    *
    * @param schema the schema that may have been reached through {@code $ref}
    * @param executionContext the execution context
    * @return the referencing schema, or null if none is found
    */
    private static Schema findReferencingSchema(Schema schema, ExecutionContext executionContext) {
    Iterator<Schema> ancestors = executionContext.getEvaluationSchema().descendingIterator();
    while (ancestors.hasNext()) {
    if (ancestors.next() == schema) {
    if (ancestors.hasNext()) {
    Schema candidate = ancestors.next();
    if (candidate.getSchemaNode().get(REF) != null) {
    return candidate;
    ), traversal returns
    the referencing $ref schema and then checks that schema’s own nullable property. Consequently, this now accepts null:

    {
    "$ref": "#/components/schemas/Money",
    "nullable": true
    }

    OpenAPI 3.0 requires additional properties on a Reference Object to be ignored, including nullable. The validator already implements that rule by discarding $ref siblings in
    Schema.read(). OpenAPI 3.0.3 Reference Object specification (https://spec.openapis.org/oas/v3.0.3.html#reference-object)

    I reproduced the behavioral regression:

    • Base a9c9638: correctly reports one type error.
    • PR head 4307fda: incorrectly reports zero errors.
    • The intended nullable + allOf + $ref case still passes.

    The traversal should inspect the lexical parent of a $ref wrapper—the allOf-owning Schema Object—without honoring nullable on the Reference Object itself. Add a regression test proving
    direct $ref siblings remain ignored.

Verification

Recommendation: REQUEST CHANGES before merge.

Per PR review feedback (networknt#1279), the
dynamic evaluation stack walk added to support nullable allOf + $ref
compositions incorrectly honored nullable: true declared as a direct
sibling of $ref. That's a Reference Object, and the OpenAPI 3.0
specification requires siblings of $ref to be ignored, so it must
never grant nullability on its own.

Track whether the current frame in the walk was reached via $ref and,
if so, skip checking that frame's own nullable — only its lexical
parent (the schema actually composing it, e.g. the allOf owner) is
consulted. The allOf + $ref case that motivated the original fix
still passes.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@stevehu

stevehu commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

Re-review: PR #1279

No blocking findings. Risk: MEDIUM. Recommendation: APPROVE.

The new commit 5d3d506 resolves the previous regression correctly:

  • Direct "$ref" + "nullable": true now remains rejected because the Reference Object’s own nullable is skipped.
  • "nullable": true on the allOf-owning schema still accepts null.
  • The contributor added a focused regression test that also asserts the resulting error is specifically a type error.
  • OpenAPI 3.1 remains unaffected.

Verification on the current head:

  • Full mvn -q test: passed.
  • Cached target used from nullable and non-nullable reference sites: passed.
  • Multiple chained $ref schemas: passed.
  • Direct $ref sibling through a chained reference: correctly rejected.
  • OpenAPI 3.1 null union and ignored legacy nullable: passed.
  • GitNexus reports medium blast radius because JsonNodeTypes is shared validation infrastructure, but the changed branch remains gated by the nullable-enabled dialect.

One non-blocking cleanup note: OpenApi30Test.java was normalized from CRLF to LF, making the diff look much larger than its functional change. That does not affect correctness.

GitHub currently reports the PR (#1279) as mergeable, although no remote status checks are listed.

@stevehu
stevehu merged commit 8716b5b into networknt:master Aug 27, 2026
@tomakehurst

Copy link
Copy Markdown
Contributor Author

Thanks!

@tomakehurst

Copy link
Copy Markdown
Contributor Author

Should I open a 2.x PR also?

We're still on v2, so it'd be great to have the fix there too.

@stevehu

stevehu commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

Yes. Please.

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.

A property that is nullable: true and composed via allOf referencing a named schema does not accept null value

3 participants