Skip to content

Commit 235914a

Browse files
committed
better resolver
1 parent ee27b46 commit 235914a

File tree

2 files changed

+62
-30
lines changed

2 files changed

+62
-30
lines changed

packages/schema-sdk/src/openapi.ts

Lines changed: 16 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import {
1212
Response,
1313
} from './openapi.types';
1414
import { OpenAPIOptions } from './types';
15-
import { createPathTemplateLiteral, applyJsonPatch } from './utils';
15+
import { createPathTemplateLiteral, applyJsonPatch, resolveMaybeRef } from './utils';
1616

1717
/**
1818
includes: {
@@ -106,12 +106,13 @@ export const getApiTypeNameSafe = (
106106

107107
export const getOperationReturnType = (
108108
options: OpenAPIOptions,
109+
schema: OpenAPISpec,
109110
operation: Operation,
110111
method: string
111112
) => {
112113
if (operation.responses) {
113114
if (operation.responses['200']) {
114-
const prop = operation.responses['200'];
115+
const prop = resolveMaybeRef<Response>(schema, operation.responses['200'] as any);
115116
return getResponseType(options, prop);
116117
}
117118
}
@@ -218,42 +219,26 @@ const initParams = (): ParameterInterfaces => {
218219
};
219220
};
220221

221-
// Resolve a parameter $ref against the spec's global parameters collection
222-
function resolveParameterRef(
223-
schema: OpenAPISpec,
224-
param: any
225-
): Parameter {
226-
if (param && typeof param === 'object' && (param as any).$ref) {
227-
const ref: string = (param as any).$ref;
228-
if (ref.includes('/parameters/')) {
229-
const parts = ref.split('/');
230-
const name = parts[parts.length - 1];
231-
const resolved = schema.parameters?.[name];
232-
if (resolved) return resolved as Parameter;
233-
}
234-
}
235-
return param as Parameter;
236-
}
237-
238222
export function generateOpenApiParams(
239223
options: OpenAPIOptions,
240224
schema: OpenAPISpec,
241225
path: string,
242226
pathItem: OpenAPIPathItem
243227
): t.TSInterfaceDeclaration[] {
228+
const resolvedPathItem = resolveMaybeRef<OpenAPIPathItem>(schema, pathItem as any);
244229
const opParams: OpParameterInterfaces = getOpenApiParams(
245230
options,
246231
schema,
247232
path,
248-
pathItem
233+
resolvedPathItem
249234
);
250235
const interfaces: t.TSInterfaceDeclaration[] = [];
251236
['get', 'post', 'put', 'delete', 'options', 'head', 'patch'].forEach(
252237
(method) => {
253-
if (Object.prototype.hasOwnProperty.call(pathItem, method)) {
238+
if (Object.prototype.hasOwnProperty.call(resolvedPathItem, method)) {
254239
// @ts-ignore
255-
const operation: Operation = pathItem[method];
256-
if (!shouldIncludeOperation(options, pathItem, path, method as any))
240+
const operation: Operation = (resolvedPathItem as any)[method];
241+
if (!shouldIncludeOperation(options, resolvedPathItem, path, method as any))
257242
return;
258243

259244
// @ts-ignore
@@ -352,7 +337,7 @@ export function getOpenApiParams(
352337
// BEGIN SANITIZE PARAMS
353338
pathItem.parameters = pathItem.parameters ?? [];
354339
const resolvedPathParams = pathItem.parameters.map((p) =>
355-
resolveParameterRef(schema, p as any)
340+
resolveMaybeRef<Parameter>(schema, p as any)
356341
);
357342
const pathParms = resolvedPathParams.filter((param) => param.in === 'path') ?? [];
358343
if (pathParms.length !== pathInfo.params.length) {
@@ -377,7 +362,7 @@ export function getOpenApiParams(
377362

378363
// load Path-Level params
379364
pathItem.parameters.forEach((param) => {
380-
const resolved = resolveParameterRef(schema, param as any);
365+
const resolved = resolveMaybeRef<Parameter>(schema, param as any);
381366
opParams.pathLevel[resolved.in].push(resolved);
382367
});
383368

@@ -401,7 +386,7 @@ export function getOpenApiParams(
401386
if (operation.parameters) {
402387
// Categorize parameters by 'in' field
403388
operation.parameters.forEach((param) => {
404-
const resolved = resolveParameterRef(schema, param as any);
389+
const resolved = resolveMaybeRef<Parameter>(schema, param as any);
405390
opParamMethod[resolved.in].push(resolved);
406391
});
407392
}
@@ -463,6 +448,7 @@ const getOperationTypeName = (
463448

464449
export const createOperation = (
465450
options: OpenAPIOptions,
451+
schema: OpenAPISpec,
466452
operation: Operation,
467453
path: string,
468454
method: string,
@@ -493,7 +479,7 @@ export const createOperation = (
493479
(param) => param.in === 'query'
494480
);
495481

496-
const returnType = getOperationReturnType(options, operation, method);
482+
const returnType = getOperationReturnType(options, schema, operation, method);
497483
const methodName = getOperationMethodName(options, operation, method, path);
498484

499485
const callMethod = t.callExpression(
@@ -559,10 +545,10 @@ export function generateMethods(
559545

560546
if (alias) {
561547
methods.push(
562-
createOperation(options, operation, path, method, alias)
548+
createOperation(options, schema, operation, path, method, alias)
563549
);
564550
}
565-
methods.push(createOperation(options, operation, path, method));
551+
methods.push(createOperation(options, schema, operation, path, method));
566552
});
567553
});
568554

@@ -709,7 +695,7 @@ export function collectReactQueryHookComponents(
709695
),
710696
]);
711697
const requestTypeName = getOperationTypeName(options, operation, method, path) + 'Request';
712-
const returnTypeAST = getOperationReturnType(options, operation, method);
698+
const returnTypeAST = getOperationReturnType(options, schema, operation, method);
713699
const methodName = opMethodName;
714700

715701
const importDecls: t.ImportDeclaration[] = [

packages/schema-sdk/src/utils.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import * as t from '@babel/types';
22
import * as jsonpatch from 'fast-json-patch';
33

44
import { OpenAPIOptions } from './types';
5+
import type { OpenAPISpec } from './openapi.types';
56

67
/**
78
* Converts a URL path with placeholders into a Babel AST TemplateLiteral.
@@ -84,3 +85,48 @@ export function applyJsonPatch<T>(spec: T, options: OpenAPIOptions): T {
8485
throw new Error(`Failed to apply JSON patches: ${error instanceof Error ? error.message : String(error)}`);
8586
}
8687
}
88+
89+
/**
90+
* Resolve an in-spec $ref object against OpenAPI v2 sections (definitions, parameters, responses).
91+
* If the provided object is not a $ref, it is returned as-is.
92+
*/
93+
export function resolveRefObject<T>(spec: OpenAPISpec, obj: any): T {
94+
let current: any = obj;
95+
let depth = 0;
96+
const MAX_DEPTH = 8;
97+
while (current && typeof current === 'object' && current.$ref && depth < MAX_DEPTH) {
98+
const ref: string = current.$ref as string;
99+
if (!ref.startsWith('#/')) break;
100+
const parts = ref.slice(2).split('/'); // remove leading '#/'
101+
const section = parts[0];
102+
const key = decodeURIComponent(parts.slice(1).join('/'));
103+
let resolved: any;
104+
switch (section) {
105+
case 'definitions':
106+
resolved = spec.definitions?.[key];
107+
break;
108+
case 'parameters':
109+
resolved = spec.parameters?.[key];
110+
break;
111+
case 'responses':
112+
resolved = spec.responses?.[key];
113+
break;
114+
default:
115+
resolved = undefined;
116+
}
117+
if (!resolved) break;
118+
current = resolved;
119+
depth += 1;
120+
}
121+
return current as T;
122+
}
123+
124+
/**
125+
* Convenience wrapper to resolve an object if it is a $ref, otherwise return it unchanged.
126+
*/
127+
export function resolveMaybeRef<T>(spec: OpenAPISpec, obj: any): T {
128+
if (obj && typeof obj === 'object' && (obj as any).$ref) {
129+
return resolveRefObject<T>(spec, obj);
130+
}
131+
return obj as T;
132+
}

0 commit comments

Comments
 (0)