Skip to content

Sibling required keyword is dropped when a property uses $ref to an external schema #866

Description

@evanmarshall

What's wrong

When a property both references an external schema with $ref and adds its own
sibling keywords (like required), the serializer throws away those sibling
keywords. An object that is missing a required field is accepted and serialized
as valid instead of raising a validation error, so incomplete data passes
through silently.

Where

index.js, in buildValue:
https://github.com/fastify/fast-json-stringify/blob/7fad3c2d6305/index.js#L1300-L1303

if (schema.$ref) {
  location = resolveRef(context, location)
  schema = location.schema
}

Resolving the reference replaces the local schema wholesale, so any sibling
keywords on the referencing schema (for example required) are gone before the
serializer is generated. The getValidatorSchemaRef guard added recently
(index.js:113, treats a schema with more than one key as non-pure) correctly
protects validator reference selection in buildOneOf/buildIfThenElse, but it
does not stop buildValue from dereferencing and discarding siblings.

How it manifests

  1. Register an external Foo schema that requires value.
  2. Create a root schema with a property x that uses $ref: Foo plus a sibling
    required: ['extra'].
  3. Build the serializer with Foo registered as an external schema.
  4. Serialize { x: { value: 1, extra: "x" } } and then { x: { value: 1 } }.

Both inputs serialize successfully, even though the second is missing the
sibling-required extra.

Expected behavior

The sibling required constraint should still apply. When a $ref has sibling
keywords, resolve the reference as the base schema and then apply the sibling
constraints on top, keeping the direct-dereference optimization only for a
schema that contains $ref alone.

Found while running Ito (AI code review, free for open source) against recently merged PRs. Full analysis: https://app.ito.ai/share/29ad67b0-500d-45d1-9ffd-df265504fb5f.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions