From 3fe6c139360405d24ae667b98554246b00a1269e Mon Sep 17 00:00:00 2001 From: Kenneth Aasan Date: Tue, 19 Mar 2024 15:59:50 +0100 Subject: [PATCH] feat: adds useSchemaTitleAsRef in the bundler --- .changeset/five-snails-divide.md | 5 +++ .../__snapshots__/bundle.test.ts.snap | 30 +++++++++++++++++ packages/core/src/__tests__/bundle.test.ts | 10 ++++++ .../fixtures/refs/basketball/match.yaml | 2 ++ .../fixtures/refs/football/match.yaml | 2 ++ ...with-external-refs-using-title-as-ref.yaml | 18 +++++++++++ packages/core/src/bundle.ts | 32 +++++++++++++------ 7 files changed, 89 insertions(+), 10 deletions(-) create mode 100644 .changeset/five-snails-divide.md create mode 100644 packages/core/src/__tests__/fixtures/refs/basketball/match.yaml create mode 100644 packages/core/src/__tests__/fixtures/refs/football/match.yaml create mode 100644 packages/core/src/__tests__/fixtures/refs/openapi-with-external-refs-using-title-as-ref.yaml diff --git a/.changeset/five-snails-divide.md b/.changeset/five-snails-divide.md new file mode 100644 index 000000000..bb0f92cac --- /dev/null +++ b/.changeset/five-snails-divide.md @@ -0,0 +1,5 @@ +--- +"@redocly/openapi-core": minor +--- + +adds useSchemaTitleAsRef in the bundler which uses the schema title as ref instead of the file name diff --git a/packages/core/src/__tests__/__snapshots__/bundle.test.ts.snap b/packages/core/src/__tests__/__snapshots__/bundle.test.ts.snap index 86b3f6faf..a44d00b76 100644 --- a/packages/core/src/__tests__/__snapshots__/bundle.test.ts.snap +++ b/packages/core/src/__tests__/__snapshots__/bundle.test.ts.snap @@ -233,6 +233,36 @@ components: `; +exports[`bundle should bundle external refs and use schema title if useSchemaTitleAsRef is true 1`] = ` +openapi: 3.1.0 +paths: + /football/match: + get: + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/FootballMatch' + /basketball/match: + get: + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/BasketballMatch' +components: + schemas: + FootballMatch: + title: FootballMatch + type: object + BasketballMatch: + title: BasketballMatch + type: object + +`; + exports[`bundle should bundle external refs and warn for conflicting names 1`] = ` openapi: 3.0.0 paths: diff --git a/packages/core/src/__tests__/bundle.test.ts b/packages/core/src/__tests__/bundle.test.ts index 99a73fd06..a4286733e 100644 --- a/packages/core/src/__tests__/bundle.test.ts +++ b/packages/core/src/__tests__/bundle.test.ts @@ -74,6 +74,16 @@ describe('bundle', () => { expect(res.parsed).toMatchSnapshot(); }); + it('should bundle external refs and use schema title if useSchemaTitleAsRef is true', async () => { + const { bundle: res, problems } = await bundle({ + config: new Config({} as ResolvedConfig), + ref: path.join(__dirname, 'fixtures/refs/openapi-with-external-refs-using-title-as-ref.yaml'), + useSchemaTitleAsRef: true, + }); + expect(problems).toHaveLength(0); + expect(res.parsed).toMatchSnapshot(); + }); + it('should dereferenced correctly when used with dereference', async () => { const { bundle: res, problems } = await bundleDocument({ externalRefResolver: new BaseResolver(), diff --git a/packages/core/src/__tests__/fixtures/refs/basketball/match.yaml b/packages/core/src/__tests__/fixtures/refs/basketball/match.yaml new file mode 100644 index 000000000..17d7e6e12 --- /dev/null +++ b/packages/core/src/__tests__/fixtures/refs/basketball/match.yaml @@ -0,0 +1,2 @@ +title: BasketballMatch +type: object diff --git a/packages/core/src/__tests__/fixtures/refs/football/match.yaml b/packages/core/src/__tests__/fixtures/refs/football/match.yaml new file mode 100644 index 000000000..02beaf6cf --- /dev/null +++ b/packages/core/src/__tests__/fixtures/refs/football/match.yaml @@ -0,0 +1,2 @@ +title: FootballMatch +type: object diff --git a/packages/core/src/__tests__/fixtures/refs/openapi-with-external-refs-using-title-as-ref.yaml b/packages/core/src/__tests__/fixtures/refs/openapi-with-external-refs-using-title-as-ref.yaml new file mode 100644 index 000000000..0aeddc837 --- /dev/null +++ b/packages/core/src/__tests__/fixtures/refs/openapi-with-external-refs-using-title-as-ref.yaml @@ -0,0 +1,18 @@ +openapi: 3.1.0 +paths: + /football/match: + get: + responses: + 200: + content: + application/json: + schema: + $ref: ./football/match.yaml + /basketball/match: + get: + responses: + 200: + content: + application/json: + schema: + $ref: ./basketball/match.yaml diff --git a/packages/core/src/bundle.ts b/packages/core/src/bundle.ts index 0c80f6e71..ae0c7d280 100755 --- a/packages/core/src/bundle.ts +++ b/packages/core/src/bundle.ts @@ -41,6 +41,7 @@ export type BundleOptions = { skipRedoclyRegistryRefs?: boolean; removeUnusedComponents?: boolean; keepUrlRefs?: boolean; + useSchemaTitleAsRef?: boolean; }; export async function bundleConfig(document: Document, resolvedRefMap: ResolvedRefMap) { @@ -149,6 +150,7 @@ export async function bundleDocument(opts: { skipRedoclyRegistryRefs?: boolean; removeUnusedComponents?: boolean; keepUrlRefs?: boolean; + useSchemaTitleAsRef?: boolean; }): Promise { const { document, @@ -159,6 +161,7 @@ export async function bundleDocument(opts: { skipRedoclyRegistryRefs = false, removeUnusedComponents = false, keepUrlRefs = false, + useSchemaTitleAsRef = false, } = opts; const specVersion = detectSpec(document.parsed); const specMajorVersion = getMajorSpecVersion(specVersion); @@ -222,7 +225,8 @@ export async function bundleDocument(opts: { skipRedoclyRegistryRefs, document, resolvedRefMap, - keepUrlRefs + keepUrlRefs, + useSchemaTitleAsRef ), }, ...decorators, @@ -316,7 +320,8 @@ function makeBundleVisitor( skipRedoclyRegistryRefs: boolean, rootDocument: Document, resolvedRefMap: ResolvedRefMap, - keepUrlRefs: boolean + keepUrlRefs: boolean, + useSchemaTitleAsRef: boolean ) { let components: Record>; let rootLocation: Location; @@ -351,10 +356,10 @@ function makeBundleVisitor( replaceRef(node, resolved, ctx); } else { if (dereference) { - saveComponent(componentType, resolved, ctx); + saveComponent(componentType, resolved, ctx, useSchemaTitleAsRef); replaceRef(node, resolved, ctx); } else { - node.$ref = saveComponent(componentType, resolved, ctx); + node.$ref = saveComponent(componentType, resolved, ctx, useSchemaTitleAsRef); resolveBundledComponent(node, resolved, ctx); } } @@ -385,9 +390,9 @@ function makeBundleVisitor( const componentType = mapTypeToComponent('Schema', version)!; if (dereference) { - saveComponent(componentType, resolved, ctx); + saveComponent(componentType, resolved, ctx, useSchemaTitleAsRef); } else { - mapping[name] = saveComponent(componentType, resolved, ctx); + mapping[name] = saveComponent(componentType, resolved, ctx, useSchemaTitleAsRef); } } }, @@ -408,10 +413,11 @@ function makeBundleVisitor( function saveComponent( componentType: string, target: { node: any; location: Location }, - ctx: UserContext + ctx: UserContext, + useSchemaTitleAsRef: boolean ) { components[componentType] = components[componentType] || {}; - const name = getComponentName(target, componentType, ctx); + const name = getComponentName(target, componentType, ctx, useSchemaTitleAsRef); components[componentType][name] = target.node; if (version === SpecMajorVersion.OAS3) { return `#/components/${componentType}/${name}`; @@ -439,7 +445,8 @@ function makeBundleVisitor( function getComponentName( target: { node: any; location: Location }, componentType: string, - ctx: UserContext + ctx: UserContext, + useSchemaTitleAsRef: boolean ) { const [fileRef, pointer] = [target.location.source.absoluteRef, target.location.pointer]; const componentsGroup = components[componentType]; @@ -458,7 +465,12 @@ function makeBundleVisitor( } } - name = refBaseName(fileRef) + (name ? `_${name}` : ''); + if (useSchemaTitleAsRef && typeof target.node.title === 'string') { + name = target.node.title; + } else { + name = refBaseName(fileRef) + (name ? `_${name}` : ''); + } + if (!componentsGroup[name] || isEqualOrEqualRef(componentsGroup[name], target, ctx)) { return name; }