Skip to content

[Bug]: payloads preset inlines duplicate $id into theCodeGenSchema, breaking AJV createValidator() #371

@jonaslagoni

Description

@jonaslagoni

Description

When the same shared JSON Schema (with its own $id) is referenced from two sibling subschemas inside a payload, the payloads preset inlines two literal copies of that schema — both keeping the same $id — into the parent payload's static theCodeGenSchema. AJV rejects the resulting schema at ajv.compile(...) time with reference "<id>" resolves to more than one schema, which makes the generated createValidator() throw. Any consumer that calls <Payload>.createValidator() (including the channels preset's subscription setup) crashes on first invocation.

Environment

  • CLI Version: @the-codegen-project/cli@0.72.0 (current latest)
  • Node Version: v22.14.0
  • OS: Windows 11

Minimal Reproduction

input.asyncapi.json

{
  "asyncapi": "3.0.0",
  "info": { "title": "Duplicate $id repro", "version": "0.1.0" },
  "channels": {
    "StartRunCommand": {
      "address": "runs.command.start",
      "messages": { "startRun": { "$ref": "#/components/messages/startRun" } }
    }
  },
  "operations": {
    "startRun": {
      "action": "receive",
      "channel": { "$ref": "#/channels/StartRunCommand" },
      "messages": [{ "$ref": "#/channels/StartRunCommand/messages/startRun" }]
    }
  },
  "components": {
    "messages": {
      "startRun": {
        "name": "startRun",
        "payload": {
          "$schema": "http://json-schema.org/draft-07/schema#",
          "type": "object",
          "properties": {
            "generatorA": {
              "$schema": "http://json-schema.org/draft-07/schema#",
              "$id": "GeneratorA",
              "type": "object",
              "properties": {
                "documentContext": {
                  "$schema": "http://json-schema.org/draft-07/schema#",
                  "$id": "DocumentContext",
                  "type": "object",
                  "properties": { "specificationDocument": { "type": "string" } }
                }
              }
            },
            "generatorB": {
              "$schema": "http://json-schema.org/draft-07/schema#",
              "$id": "GeneratorB",
              "type": "object",
              "properties": {
                "documentContext": {
                  "$ref": "#/channels/StartRunCommand/messages/startRun/payload/properties/generatorA/properties/documentContext"
                }
              }
            }
          }
        }
      }
    }
  }
}

Note the asymmetry: generatorA.documentContext is the canonical inline subschema ($id: "DocumentContext"), and generatorB.documentContext is an internal JSON-pointer $ref to it. This is what an AsyncAPI bundler produces when two siblings both originally $ref-ed the same shared schema file.

codegen.ts

import { TheCodegenConfiguration } from '@the-codegen-project/cli'
const config: TheCodegenConfiguration = {
  inputType: 'asyncapi',
  inputPath: './input.asyncapi.json',
  language: 'typescript',
  generators: [
    {
      preset: 'payloads',
      outputPath: './out/payloads',
      serializationType: 'json',
      language: 'typescript',
    },
  ],
}
export default config

Command

codegen generate ./codegen.ts

Expected Output

Any one of the following would let AJV compile the generated theCodeGenSchema:

  1. Keep one inline definition and use a JSON-pointer $ref for subsequent occurrences (matching the input shape).
  2. Hoist the shared schema into a $defs entry on the root of theCodeGenSchema and $ref it from each site.
  3. If full dereferencing is intentional, strip $id from the inlined duplicates so AJV only sees one anchor for that schema.

Example (option 1):

public static theCodeGenSchema = {
  // ...
  "properties": {
    "generatorA": { /* ... */ "documentContext": { "$id": "DocumentContext", /* ... */ } },
    "generatorB": { /* ... */ "documentContext": { "$ref": "#/properties/generatorA/properties/documentContext" } }
  },
  "$id": "startRun"
};

Actual Output

out/payloads/StartRun.ts (relevant excerpt, formatted):

public static theCodeGenSchema = {
  "type": "object",
  "$schema": "http://json-schema.org/draft-07/schema#",
  "properties": {
    "generatorA": {
      "$schema": "http://json-schema.org/draft-07/schema#",
      "$id": "GeneratorA",
      "type": "object",
      "properties": {
        "documentContext": {
          "$schema": "http://json-schema.org/draft-07/schema#",
          "$id": "DocumentContext",         // copy 1
          "type": "object",
          "properties": { "specificationDocument": { "type": "string" } }
        }
      }
    },
    "generatorB": {
      "$schema": "http://json-schema.org/draft-07/schema#",
      "$id": "GeneratorB",
      "type": "object",
      "properties": {
        "documentContext": {
          "$schema": "http://json-schema.org/draft-07/schema#",
          "$id": "DocumentContext",         // copy 2 — same $id, different position
          "type": "object",
          "properties": { "specificationDocument": { "type": "string" } }
        }
      }
    }
  },
  "$id": "startRun"
};

public static createValidator(...): ValidateFunction {
  // ...
  const validate = ajvInstance.compile(this.theCodeGenSchema); // throws
  return validate;
}

Calling StartRun.createValidator() (or feeding the generated theCodeGenSchema directly into new Ajv().compile(...)) throws:

Error: reference "DocumentContext" resolves to more than one schema
    at ambiguos (.../ajv/lib/compile/resolve.ts:147:12)
    at Ajv.addRef (.../ajv/lib/compile/resolve.ts:115:38)
    ...
    at Ajv.compile (.../ajv/lib/core.ts:388:22)

Impact

Any service consuming payloads runtime validation on a payload whose schema tree contains a shared $id-bearing schema referenced from two siblings will crash at startup the first time <Payload>.createValidator() is called. The channels preset calls createValidator() eagerly inside subscription setup, so the failure surfaces immediately — not only when a message arrives.

The same shape also produces duplicate $id: "npm-release" etc. in real specs (with multiple shared schemas referenced from multiple generator subschemas); AJV simply reports the first conflict it hits.

Additional Context

  • I searched the repo for DocumentContext, duplicate $id, resolves to more than one schema, ajv duplicate, theCodeGenSchema, inline schema duplicate, and payloads validate. No existing issue covers this.
  • Tested with @the-codegen-project/cli@0.72.0 (latest). The behavior reproduces; nothing in v0.64.x→v0.72.0 release notes mentions related fixes.
  • Verified the AJV failure independently by extracting the generated theCodeGenSchema literal and passing it to new Ajv().compile(...).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions