Skip to content

Recursive validation schema compiles but fails at runtime #648

Closed
@BenoitRanque

Description

@BenoitRanque

Prerequisites

  • I have written a descriptive issue title
  • I have searched existing issues to ensure the bug has not already been reported

Fastify version

4.23.2

Plugin version

No response

Node.js version

18.12.1

Operating system

Windows

Operating system version (i.e. 20.04, 11.3, 10)

11

Description

The following error is thrown when validating a response using a response schema, when trying to return a response:

{
  "statusCode": 500,
  "error": "Internal Server Error",
  "message": "can't resolve reference #/definitions/Type from id merged_45bedfdc-0c19-4649-8739-66c2f819bb2e"
}

This is unexpected: it's my understanding that schemas should be validated on server startup.
The error also does not throw if the response does not include the problematic parts of the schema.

This is most likely an AJV bug, but I was not able to reproduce it trivially. I would appreciate help with isolating the Fastify-AJV interaction to reproduce this without the Fastify server at all.

Steps to Reproduce

Full sample code:

import Fastify from "fastify";

const schema = {
  $schema: "http://json-schema.org/draft-07/schema#",
  title: "Schema Response",
  type: "object",
  required: [],
  properties: {
    types: {
      description: "A list of types.",
      type: "object",
      additionalProperties: {
        $ref: "#/definitions/Type",
      },
    },
  },
  definitions: {
    Type: {
      title: "Type",
      description: "Types track the valid representations of values as JSON",
      oneOf: [
        {
          description: "A named type",
          type: "object",
          required: ["name", "type"],
          properties: {
            name: {
              description:
                "The name can refer to a primitive type or a scalar type",
              type: "string",
            },
            type: {
              type: "string",
              enum: ["named"],
            },
          },
        },
        {
          description: "A nullable type",
          type: "object",
          required: ["type", "underlying_type"],
          properties: {
            type: {
              type: "string",
              enum: ["nullable"],
            },
            underlying_type: {
              description: "The type of the non-null inhabitants of this type",
              allOf: [
                {
                  $ref: "#/definitions/Type",
                },
              ],
            },
          },
        },
      ],
    },
  },
};

const data = {
  types: {
    Int: {
      type: "named",
      name: "Int",
    },
    NullableString: {
      type: "nullable",
      underlying_type: {
        type: "named",
        name: "String",
      },
    },
  },
};

async function start_server() {
  const server = Fastify({
    logger: true,
  });

  server.get(
    "/",
    {
      schema: {
        response: {
          200: schema,
        },
      },
    },
    () => {
      return data;
    }
  );

  try {
    await server.listen({ port: 3030 });
  } catch (error) {
    server.log.error(error);
    process.exit(1);
  }
}

start_server();

Fastify version: 4.23.2
To reproduce the error, run the server, and perform a GET request against it.

Expected Behavior

The response should be validated successfully, or it should fail to validate, but not fail to resolve the validation schema, except on server startup.

Note, the following using only AJV does not error:

const schema = { /* same as above */ }
const data = { /* same as above */ }

const validate = ajv.compile(schema);
const valid = validate(data);
if (!valid) {
  console.log("validation failed");
  console.log(validate.errors);
} else {
  console.log("validation succeeded");
}

I strongly suspect this is an AJV issue, but as the above did not repro the problem I would appreciate any help in isolating the issue.

Workarounds

I've observed two ways to avoid this issue:

  1. use $recursiveRef instead of $ref. I don't believe this is the correct use case for $recursiveRef, but it does make the problem go away
  2. specify an $id on the root, then qualify $refs with the id before the #

Possibly related issues

Using discriminator in a definition causes failure to compile if oneOf contains a reference fastify/fastify#1899
Is it possible to create a recursive schema definition? fastify/fastify#659

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugConfirmed bughelp wantedHelp the community by contributing to this issue

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions