fix(openapi): extract inline definitions and $defs into components.schemas - #943
Open
lx3133584 wants to merge 1 commit into
Open
fix(openapi): extract inline definitions and $defs into components.schemas#943lx3133584 wants to merge 1 commit into
lx3133584 wants to merge 1 commit into
Conversation
…hemas Fixes fastify#865 Signed-off-by: Liang Xu <lx3133584@users.noreply.github.com>
There was a problem hiding this comment.
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
definitionsand$defsintocomponents.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)withoutopenapiObject. Consequently, definitions nested underitems,allOf/oneOf,additionalProperties, or any keyword other thanproperties/patternPropertiesare 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.jsonis corrupted intohttps://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-0while 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 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 { |
| 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 => { |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
When using recursive schemas (such as TypeBox
Type.Recursive) or schemas containing inlinedefinitionsor$defs, the generated OpenAPI 3.0 document rewrites internal$refpointers (e.g.#/definitions/def-0to#/components/schemas/def-0), but deletes the underlying definition objects without placing them incomponents.schemas. This leaves dangling$refreferences that fail OpenAPI document validation.Root Cause
convertJsonSchemaToOpenapi3previously unconditionally deleteddefinitionswithout preserving and registering them inopenapiObject.components.schemas, and nested recursive conversion calls did not passopenapiObjectdown to child properties.Fix
convertJsonSchemaToOpenapi3to accept and threadopenapiObjectthrough recursive schema conversions.definitionsor$defskeys are encountered on a schema, their items are converted to OpenAPI 3.0 schemas and added toopenapiObject.components.schemas.$defsalongsidedefinitionswhen normalizing$refpointers to#/components/schemas/.Testing
test/spec/openapi/schema.test.jsvalidating that inline definitions are extracted tocomponents.schemasand pass full Swagger/OpenAPI validation.