Skip to content

Commit d353e46

Browse files
committed
chore(schema): add functionality for handling $ref to /path
This was added to @hey-api/json-schema-ref-parser 1.0.8 but I'm moving it directly to openapi-ts
1 parent a312623 commit d353e46

File tree

9 files changed

+585
-369
lines changed

9 files changed

+585
-369
lines changed

packages/openapi-ts-tests/main/test/3.1.x.test.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,13 @@ describe(`OpenAPI ${version}`, () => {
4444
};
4545

4646
const scenarios = [
47+
{
48+
config: createConfig({
49+
input: 'external.yaml',
50+
output: 'external',
51+
}),
52+
description: 'handles external references',
53+
},
4754
{
4855
config: createConfig({
4956
input: 'pattern-properties.json',
@@ -924,6 +931,7 @@ describe(`OpenAPI ${version}`, () => {
924931
];
925932

926933
it.each(scenarios)('$description', async ({ config }) => {
934+
// it.each([scenarios[0]])('$description', async ({ config }) => {
927935
await createClient(config);
928936

929937
const outputPath =
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
// This file is auto-generated by @hey-api/openapi-ts
2+
export * from './types.gen';
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
// This file is auto-generated by @hey-api/openapi-ts
2+
3+
/**
4+
* External schema (A)
5+
*/
6+
export type ExternalSchemaA = {
7+
id: string;
8+
name?: string;
9+
};
10+
11+
/**
12+
* External schema (B)
13+
*/
14+
export type ExternalSchemaB = ExternalSchemaA;
15+
16+
/**
17+
* External schema (C)
18+
*/
19+
export type ExternalSchemaC = ExternalSchemaA;
20+
21+
/**
22+
* External schema property (A)
23+
*/
24+
export type ExternalSchemaPropertyA = {
25+
0?: string;
26+
};
27+
28+
/**
29+
* External schema property (B)
30+
*/
31+
export type ExternalSchemaPropertyB = {
32+
0?: ;
33+
};
34+
35+
/**
36+
* External schema property from component (A.id)
37+
*/
38+
export type ExternalSchemaPropertyFromComponent = {
39+
0?: ;
40+
};
41+
42+
/**
43+
* External schema property with duplicate refs (D)
44+
*/
45+
export type ExternalSchemaPropertyD = {
46+
0?: ;
47+
1?: ;
48+
};
49+
50+
/**
51+
* External schema property via external property ref (id)
52+
*/
53+
export type ExternalSchemaExternalProp = {
54+
0?: ;
55+
};
56+
57+
/**
58+
* Alias to external property via component property ref
59+
*/
60+
export type ExternalSchemaExternalPropAlias = {
61+
0?: ;
62+
};
63+
64+
/**
65+
* External double nested prop via property ref
66+
*/
67+
export type ExternalDoubleNestedProp = {
68+
0?: string;
69+
};
70+
71+
/**
72+
* External double nested numeric properties
73+
*/
74+
export type ExternalDoubleNestedNumeric = {
75+
0?: string;
76+
};
77+
78+
export type ClientOptions = {
79+
baseUrl: `${string}://${string}` | (string & {});
80+
};

packages/openapi-ts-tests/specs/3.1.x/external-shared.json

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,31 @@
66
"name": { "type": "string" }
77
},
88
"required": ["id"]
9+
},
10+
"ExternalSharedModelWithUuid": {
11+
"type": "string",
12+
"format": "uuid"
13+
},
14+
"ExternalNested": {
15+
"type": "object",
16+
"properties": {
17+
"inner": {
18+
"type": "object",
19+
"properties": {
20+
"deep": { "type": "string" }
21+
}
22+
}
23+
}
24+
},
25+
"ExternalNestedNumeric": {
26+
"type": "object",
27+
"properties": {
28+
"0": {
29+
"type": "object",
30+
"properties": {
31+
"1": { "type": "string" }
32+
}
33+
}
34+
}
935
}
1036
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
openapi: 3.1.1
2+
info:
3+
title: OpenAPI 3.1.1 external example
4+
version: 1
5+
components:
6+
schemas:
7+
ExternalSchemaA:
8+
description: External schema (A)
9+
$ref: './external-shared.json#/ExternalSharedModel'
10+
ExternalSchemaB:
11+
description: External schema (B)
12+
$ref: './external-shared.json#/ExternalSharedModel'
13+
ExternalSchemaC:
14+
description: External schema (C)
15+
$ref: './external-shared.json#/ExternalSharedModel'
16+
ExternalSchemaPropertyA:
17+
description: External schema property (A)
18+
type: object
19+
properties:
20+
- $ref: './external-shared.json#/ExternalSharedModelWithUuid'
21+
ExternalSchemaPropertyB:
22+
description: External schema property (B)
23+
type: object
24+
properties:
25+
- $ref: './external-shared.json#/ExternalSharedModelWithUuid'
26+
ExternalSchemaPropertyFromComponent:
27+
description: External schema property from component (A.id)
28+
type: object
29+
properties:
30+
- $ref: '#/components/schemas/ExternalSchemaA/properties/id'
31+
ExternalSchemaPropertyD:
32+
description: External schema property with duplicate refs (D)
33+
type: object
34+
properties:
35+
- $ref: './external-shared.json#/ExternalSharedModelWithUuid'
36+
- $ref: './external-shared.json#/ExternalSharedModelWithUuid'
37+
ExternalSchemaExternalProp:
38+
description: External schema property via external property ref (id)
39+
type: object
40+
properties:
41+
- $ref: './external-shared.json#/ExternalSharedModel/properties/id'
42+
ExternalSchemaExternalPropAlias:
43+
description: Alias to external property via component property ref
44+
type: object
45+
properties:
46+
- $ref: '#/components/schemas/ExternalSchemaExternalProp/properties/0'
47+
ExternalDoubleNestedProp:
48+
description: External double nested prop via property ref
49+
type: object
50+
properties:
51+
- $ref: './external-shared.json#/ExternalNested/properties/inner/properties/deep'
52+
ExternalDoubleNestedNumeric:
53+
description: External double nested numeric properties
54+
type: object
55+
properties:
56+
- $ref: './external-shared.json#/ExternalNestedNumeric/properties/0/properties/1'

packages/openapi-ts/src/openApi/2.0.x/parser/schema.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -588,6 +588,23 @@ const parseRef = ({
588588
state: SchemaState;
589589
}): IR.SchemaObject => {
590590
const irSchema: IR.SchemaObject = {};
591+
// Inline non-component refs (e.g. #/paths/...) to avoid generating orphaned named types
592+
const isComponentsRef = schema.$ref.startsWith('#/definitions/');
593+
if (!isComponentsRef) {
594+
if (!state.circularReferenceTracker.has(schema.$ref)) {
595+
const refSchema = context.resolveRef<SchemaObject>(schema.$ref);
596+
return schemaToIrSchema({
597+
context,
598+
schema: refSchema,
599+
state: {
600+
...state,
601+
$ref: schema.$ref,
602+
isProperty: false,
603+
},
604+
});
605+
}
606+
// Fallback to preserving the ref if circular
607+
}
591608

592609
// refs using unicode characters become encoded, didn't investigate why
593610
// but the suspicion is this comes from `@hey-api/json-schema-ref-parser`

packages/openapi-ts/src/openApi/3.0.x/parser/schema.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -818,6 +818,24 @@ const parseRef = ({
818818
schema: ReferenceObject;
819819
state: SchemaState;
820820
}): IR.SchemaObject => {
821+
// Inline non-component refs (e.g. #/paths/...) to avoid generating orphaned named types
822+
const isComponentsRef = schema.$ref.startsWith('#/components/');
823+
if (!isComponentsRef) {
824+
if (!state.circularReferenceTracker.has(schema.$ref)) {
825+
const refSchema = context.resolveRef<SchemaObject>(schema.$ref);
826+
return schemaToIrSchema({
827+
context,
828+
schema: refSchema,
829+
state: {
830+
...state,
831+
$ref: schema.$ref,
832+
isProperty: false,
833+
},
834+
});
835+
}
836+
// Fallback to preserving the ref if circular
837+
}
838+
821839
const irSchema: IR.SchemaObject = {};
822840

823841
// refs using unicode characters become encoded, didn't investigate why

packages/openapi-ts/src/openApi/3.1.x/parser/schema.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -810,6 +810,24 @@ const parseRef = ({
810810
schema: SchemaWithRequired<SchemaObject, '$ref'>;
811811
state: SchemaState;
812812
}): IR.SchemaObject => {
813+
// Inline non-component refs (e.g. #/paths/...) to avoid generating orphaned named types
814+
const isComponentsRef = schema.$ref.startsWith('#/components/');
815+
if (!isComponentsRef) {
816+
if (!state.circularReferenceTracker.has(schema.$ref)) {
817+
const refSchema = context.resolveRef<SchemaObject>(schema.$ref);
818+
return schemaToIrSchema({
819+
context,
820+
schema: refSchema,
821+
state: {
822+
...state,
823+
$ref: schema.$ref,
824+
isProperty: false,
825+
},
826+
});
827+
}
828+
// Fallback to preserving the ref if circular
829+
}
830+
813831
let irSchema = initIrSchema({ schema });
814832

815833
const irRefSchema: IR.SchemaObject = {};

0 commit comments

Comments
 (0)