Skip to content

Commit 1bd8015

Browse files
Merge pull request #2558 from kubb-labs/copilot/sub-pr-2555
2 parents 2a07b8d + 3f40fda commit 1bd8015

6 files changed

Lines changed: 83 additions & 10 deletions

File tree

packages/oas/mocks/category.yaml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
openapi: 3.0.3
2+
info:
3+
title: Category
4+
version: 1.0.0
5+
components:
6+
schemas:
7+
Category:
8+
type: object
9+
properties:
10+
id:
11+
type: integer
12+
format: int64
13+
name:
14+
type: string
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
openapi: 3.0.3
2+
info:
3+
title: PetStore with external file ref
4+
version: 1.0.0
5+
paths: {}
6+
components:
7+
schemas:
8+
Pet:
9+
type: object
10+
required:
11+
- id
12+
- name
13+
properties:
14+
id:
15+
type: integer
16+
format: int64
17+
name:
18+
type: string
19+
category:
20+
$ref: './category.yaml#/components/schemas/Category'

packages/oas/src/utils.spec.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,39 @@ components:
8282
)
8383
expect(oas.api?.info.title).toBe('Swagger PetStore')
8484
})
85+
86+
test('parse a spec with an external file $ref preserves the $ref pointer', async () => {
87+
const specPath = path.resolve(__dirname, '../mocks/petStoreExternalFileRef.yaml')
88+
const oas = await parse(specPath)
89+
90+
expect(oas).toBeDefined()
91+
expect(oas.api?.info.title).toBe('PetStore with external file ref')
92+
// The $ref pointer must be preserved (not inlined) so SchemaGenerator can emit a named type reference
93+
const petSchema = (oas.api as any).components?.schemas?.Pet
94+
expect(petSchema).toBeDefined()
95+
expect(petSchema.type).toBe('object')
96+
expect(petSchema.required).toEqual(['id', 'name'])
97+
expect(petSchema.properties.id).toEqual({ type: 'integer', format: 'int64' })
98+
expect(petSchema.properties.name).toEqual({ type: 'string' })
99+
expect(petSchema.properties.category.$ref).toBe('./category.yaml#/components/schemas/Category')
100+
})
101+
102+
test('parse a spec with an external URL $ref preserves the $ref pointer', async () => {
103+
const specPath = path.resolve(__dirname, '../../plugin-ts/mocks/petStore.yaml')
104+
const oas = await parse(specPath)
105+
106+
expect(oas).toBeDefined()
107+
expect(oas.api?.info.title).toBe('Swagger PetStore')
108+
// The external HTTP $ref must be preserved (not inlined) so SchemaGenerator can emit a named type reference
109+
const petSchema = (oas.api as any).components?.schemas?.Pet
110+
expect(petSchema).toBeDefined()
111+
expect(petSchema.type).toBe('object')
112+
expect(petSchema.required).toEqual(['id', 'name'])
113+
expect(petSchema.properties.id).toEqual({ type: 'integer', format: 'int64' })
114+
expect(petSchema.properties.name).toEqual({ type: 'string' })
115+
expect(petSchema.properties.tag).toEqual({ type: 'string' })
116+
expect(petSchema.properties.category.$ref).toBe('https://petstore3.swagger.io/api/v3/openapi.json#/components/schemas/Category')
117+
})
85118
})
86119

87120
describe('parseFromConfig', () => {

packages/oas/src/utils.ts

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -162,14 +162,8 @@ export function getDefaultValue(schema?: SchemaObject): string | undefined {
162162

163163
export async function parse(
164164
pathOrApi: string | Document,
165-
{ oasClass = Oas, canBundle = true, enablePaths = true }: { oasClass?: typeof Oas; canBundle?: boolean; enablePaths?: boolean } = {},
165+
{ oasClass = Oas, enablePaths = true }: { oasClass?: typeof Oas; canBundle?: boolean; enablePaths?: boolean } = {},
166166
): Promise<Oas> {
167-
if (typeof pathOrApi === 'string' && canBundle) {
168-
// resolve external refs using oas-normalize (already a dependency)
169-
const bundled = await new OASNormalize(pathOrApi, { enablePaths, colorizeErrors: true }).bundle()
170-
return parse(bundled as Document, { oasClass, canBundle: false, enablePaths })
171-
}
172-
173167
const oasNormalize = new OASNormalize(pathOrApi, {
174168
enablePaths,
175169
colorizeErrors: true,
@@ -188,7 +182,7 @@ export async function parse(
188182
}
189183

190184
export async function merge(pathOrApi: Array<string | Document>, { oasClass = Oas }: { oasClass?: typeof Oas } = {}): Promise<Oas> {
191-
const instances = await Promise.all(pathOrApi.map((p) => parse(p, { oasClass, enablePaths: false, canBundle: false })))
185+
const instances = await Promise.all(pathOrApi.map((p) => parse(p, { oasClass, enablePaths: false })))
192186

193187
if (instances.length === 0) {
194188
throw new Error('No OAS instances provided for merging.')

packages/plugin-oas/src/plugin.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,15 @@ export const pluginOas = definePlugin<PluginOas>((options) => {
130130
return
131131
}
132132

133-
await oas.dereference()
133+
await oas.dereference().catch((err: unknown) => {
134+
// Some specs have invalid internal $ref pointers (e.g. `#/definitions/...` in OpenAPI 3.x docs)
135+
// that cause dereference to fail. Log the issue and continue — the SchemaGenerator resolves
136+
// refs on-the-fly via dereferenceWithRef(), so schema generation still works.
137+
this.events.emit('debug', {
138+
date: new Date(),
139+
logs: [`Unable to fully dereference schema: ${(err as Error).message}`],
140+
})
141+
})
134142

135143
const schemaGenerator = new SchemaGenerator(
136144
{

packages/plugin-redoc/src/plugin.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,11 @@ export const pluginRedoc = definePlugin<PluginRedoc>((options) => {
2222
pre: [pluginOasName],
2323
async install() {
2424
const oas = await this.getOas()
25-
await oas.dereference()
25+
await oas.dereference().catch((err: unknown) => {
26+
// Some specs have invalid internal $ref pointers (e.g. `#/definitions/...` in OpenAPI 3.x docs)
27+
// that cause dereference to fail. Continue with the un-dereferenced spec.
28+
void err
29+
})
2630

2731
const root = path.resolve(this.config.root, this.config.output.path)
2832
const pageHTML = await getPageHTML(oas.api)

0 commit comments

Comments
 (0)