Skip to content

fix(openapi): extract inline definitions and $defs into components.schemas - #943

Open
lx3133584 wants to merge 1 commit into
fastify:mainfrom
lx3133584:fix/openapi-extract-inline-definitions
Open

fix(openapi): extract inline definitions and $defs into components.schemas#943
lx3133584 wants to merge 1 commit into
fastify:mainfrom
lx3133584:fix/openapi-extract-inline-definitions

Conversation

@lx3133584

Copy link
Copy Markdown

Problem

When using recursive schemas (such as TypeBox Type.Recursive) or schemas containing inline definitions or $defs, the generated OpenAPI 3.0 document rewrites internal $ref pointers (e.g. #/definitions/def-0 to #/components/schemas/def-0), but deletes the underlying definition objects without placing them in components.schemas. This leaves dangling $ref references that fail OpenAPI document validation.

Root Cause

convertJsonSchemaToOpenapi3 previously unconditionally deleted definitions without preserving and registering them in openapiObject.components.schemas, and nested recursive conversion calls did not pass openapiObject down to child properties.

Fix

  • Updated convertJsonSchemaToOpenapi3 to accept and thread openapiObject through recursive schema conversions.
  • When definitions or $defs keys are encountered on a schema, their items are converted to OpenAPI 3.0 schemas and added to openapiObject.components.schemas.
  • Supported $defs alongside definitions when normalizing $ref pointers to #/components/schemas/.

Testing

  • Added regression test in test/spec/openapi/schema.test.js validating that inline definitions are extracted to components.schemas and pass full Swagger/OpenAPI validation.
  • Verified all 268 unit tests pass with 100% statement, branch, function, and line coverage.

…hemas

Fixes fastify#865

Signed-off-by: Liang Xu <lx3133584@users.noreply.github.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

Extracts inline JSON Schema definitions into OpenAPI components to prevent dangling references. It also changes example-object conversion behavior.

Changes:

  • Threads the OpenAPI document through schema conversion.
  • Extracts definitions and $defs into components.schemas.
  • Adds metadata handling for example objects and regression tests.

Reviewed changes

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

File Description
lib/spec/openapi/utils.js Implements schema extraction, reference rewriting, and example conversion.
test/spec/openapi/schema.test.js Tests inline definition extraction.
test/spec/openapi/option.test.js Tests named example metadata.
Suppressed comments (3)

lib/spec/openapi/utils.js:596

  • The generic recursive path below still calls convertJsonSchemaToOpenapi3(opts, value) without openapiObject. Consequently, definitions nested under items, allOf/oneOf, additionalProperties, or any keyword other than properties/patternProperties are deleted rather than extracted, leaving the rewritten refs dangling. Thread the object through the fallback recursion too.
        const propertyOpenapiSchema = convertJsonSchemaToOpenapi3(opts, propertyJsonSchema, openapiObject)

lib/spec/openapi/utils.js:556

  • The unrestricted replacements also modify non-local reference URIs containing these path segments—for example, https://example.com/$defs/model.json is corrupted into https://example.com/components/schemas/model.json. Only the internal fragment prefixes that are being relocated should be normalized.
    if (key === '$ref' && typeof value === 'string') {
      openapiSchema.$ref = value.replace('definitions', 'components/schemas').replace('$defs', 'components/schemas')

lib/spec/openapi/utils.js:542

  • Local definition keys are not globally unique, but this first-wins insertion flattens every schema into one global namespace. If two routes each contain a different def-0 (a common generated name), the second route's ref is rewritten to #/components/schemas/def-0 while this guard retains the first route's schema, producing a valid-looking document with the wrong schema. Extracted names and rewritten refs need a stable namespace (for example from the enclosing $id), or collisions must be detected instead of silently reused.
          if (!openapiObject.components.schemas[defKey]) {
            openapiObject.components.schemas[defKey] = convertJsonSchemaToOpenapi3(opts, value[defKey], openapiObject)

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

Comment thread lib/spec/openapi/utils.js
Comment on lines +124 to 132
if (typeof example === 'object' && example !== null) {
if ('value' in example || 'externalValue' in example) {
const name = example.name || example.id || ('example' + (index + 1))
const { name: _name, id: _id, ...exampleObj } = example
examplesObject[name] = exampleObj
} else {
examplesObject['example' + (index + 1)] = { value: example }
}
} else {
Comment thread lib/spec/openapi/utils.js
if (key === '$id' || key === '$schema' || key === 'definitions') {
// TODO: this breaks references to the definition properties
if (key === 'definitions' || key === '$defs') {
if (openapiObject?.components?.schemas && typeof value === 'object' && value !== null) {
})
})

test('extracts inline definitions and $defs into components.schemas for recursive schemas', async t => {
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.

2 participants