Skip to content

Commit db98304

Browse files
committed
fix(parser): prevent broken output by tracking dependencies when using filters
1 parent 33a417f commit db98304

File tree

3 files changed

+134
-5
lines changed

3 files changed

+134
-5
lines changed

packages/openapi-ts-tests/test/openapi-ts.config.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@ export default defineConfig(() => {
2525
// 'x-foo': 'bar',
2626
// },
2727
// },
28-
// include:
29-
// '^(#/components/schemas/import|#/paths/api/v{api-version}/simple/options)$',
28+
include: '^(#/components/schemas/Foo)$',
29+
// '^(#/components/schemas/Foo|#/paths/api/v{api-version}/simple/options)$',
3030
// organization: 'hey-api',
3131
// path: {
3232
// components: {},
@@ -36,7 +36,12 @@ export default defineConfig(() => {
3636
// openapi: '3.1.0',
3737
// paths: {},
3838
// },
39-
path: path.resolve(__dirname, 'spec', '3.1.x', 'full.json'),
39+
path: path.resolve(
40+
__dirname,
41+
'spec',
42+
'3.1.x',
43+
'validators-circular-ref-2.yaml',
44+
),
4045
// path: 'http://localhost:4000/',
4146
// path: 'https://get.heyapi.dev/',
4247
// path: 'https://get.heyapi.dev/hey-api/backend?branch=main&version=1.0.0',
@@ -115,7 +120,7 @@ export default defineConfig(() => {
115120
},
116121
{
117122
exportFromIndex: true,
118-
name: '@tanstack/react-query',
123+
// name: '@tanstack/react-query',
119124
},
120125
{
121126
// exportFromIndex: true,
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
import type { OpenApiV3_1_X, SchemaObject } from '../types/spec';
2+
3+
const collectSchemaDependencies = (
4+
schema: SchemaObject,
5+
dependencies: Set<string>,
6+
) => {
7+
// TODO: add more keywords, e.g. prefixItems
8+
9+
if (schema.$ref) {
10+
const refName = schema.$ref.split('/').pop();
11+
if (refName) {
12+
dependencies.add(refName);
13+
}
14+
}
15+
16+
if (schema.items) {
17+
collectSchemaDependencies(schema.items, dependencies);
18+
}
19+
20+
if (schema.properties) {
21+
for (const property of Object.values(schema.properties)) {
22+
if (typeof property !== 'boolean') {
23+
collectSchemaDependencies(property, dependencies);
24+
}
25+
}
26+
}
27+
28+
if (schema.allOf) {
29+
for (const item of schema.allOf) {
30+
collectSchemaDependencies(item, dependencies);
31+
}
32+
}
33+
34+
if (schema.anyOf) {
35+
for (const item of schema.anyOf) {
36+
collectSchemaDependencies(item, dependencies);
37+
}
38+
}
39+
40+
if (schema.oneOf) {
41+
for (const item of schema.oneOf) {
42+
collectSchemaDependencies(item, dependencies);
43+
}
44+
}
45+
};
46+
47+
// TODO: make generic to work with other categories
48+
type DependencyGraph = Map<string, Set<string>>;
49+
50+
export const createDependencyGraph = (spec: OpenApiV3_1_X): DependencyGraph => {
51+
// TODO: add paths, other components
52+
const dependencyGraph: DependencyGraph = new Map();
53+
54+
if (spec.components) {
55+
if (spec.components.schemas) {
56+
for (const [schemaName, schema] of Object.entries(
57+
spec.components.schemas,
58+
)) {
59+
const dependencies = new Set<string>();
60+
collectSchemaDependencies(schema, dependencies);
61+
dependencyGraph.set(schemaName, dependencies);
62+
}
63+
}
64+
}
65+
66+
return dependencyGraph;
67+
};
68+
69+
// TODO: make generic to work with any spec version
70+
// TODO: support exclude
71+
export const filterSpec = ({
72+
dependencyGraph,
73+
includedSchemas,
74+
spec,
75+
}: {
76+
dependencyGraph: DependencyGraph;
77+
includedSchemas: Set<string>;
78+
spec: OpenApiV3_1_X;
79+
}): OpenApiV3_1_X => {
80+
const allIncludedSchemas = new Set<string>(includedSchemas);
81+
82+
const stack = [...includedSchemas];
83+
while (stack.length) {
84+
const schemaName = stack.pop()!;
85+
const dependencies = dependencyGraph.get(schemaName);
86+
if (dependencies) {
87+
for (const dependency of dependencies) {
88+
if (!allIncludedSchemas.has(dependency)) {
89+
allIncludedSchemas.add(dependency);
90+
stack.push(dependency);
91+
}
92+
}
93+
}
94+
}
95+
96+
if (spec.components) {
97+
if (spec.components.schemas) {
98+
const schemasFiltered: typeof spec.components.schemas = {};
99+
for (const schemaName of allIncludedSchemas) {
100+
const schemaSource = spec.components.schemas[schemaName];
101+
if (schemaSource) {
102+
schemasFiltered[schemaName] = schemaSource;
103+
}
104+
}
105+
spec.components.schemas = schemasFiltered;
106+
}
107+
}
108+
109+
return spec;
110+
};

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

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,25 @@ import type {
1010
RequestBodyObject,
1111
SecuritySchemeObject,
1212
} from '../types/spec';
13+
import { createDependencyGraph, filterSpec } from './dependencyGraph';
1314
import { parseOperation } from './operation';
1415
import { parametersArrayToObject, parseParameter } from './parameter';
1516
import { parseRequestBody } from './requestBody';
1617
import { parseSchema } from './schema';
1718
import { parseServers } from './server';
19+
1820
export const parseV3_1_X = (context: IR.Context<OpenApiV3_1_X>) => {
21+
// TODO: construct includedSchemas from config
22+
const includedSchemas = new Set<string>();
23+
includedSchemas.add('Bar');
24+
25+
// TODO: skip filtering if no filters are defined
26+
const dependencyGraph = createDependencyGraph(context.spec);
27+
context.spec = filterSpec({
28+
dependencyGraph,
29+
includedSchemas,
30+
spec: context.spec,
31+
});
1932
const state: State = {
2033
ids: new Map(),
2134
operationIds: new Map(),
@@ -25,13 +38,14 @@ export const parseV3_1_X = (context: IR.Context<OpenApiV3_1_X>) => {
2538
const excludeFilters = createFilters(context.config.input.exclude);
2639
const includeFilters = createFilters(context.config.input.include);
2740

41+
// TODO: remove shouldProcessRef
2842
const shouldProcessRef = ($ref: string, schema: Record<string, any>) =>
2943
canProcessRef({
3044
$ref,
3145
excludeFilters,
3246
includeFilters,
3347
schema,
34-
});
48+
}) || true;
3549

3650
// TODO: parser - handle more component types, old parser handles only parameters and schemas
3751
if (context.spec.components) {

0 commit comments

Comments
 (0)