Skip to content

Commit 7d09c3b

Browse files
authored
fix: support nested path parameters in --path-params-as-types (#1130)
1 parent 941871a commit 7d09c3b

File tree

3 files changed

+107
-7
lines changed

3 files changed

+107
-7
lines changed

.changeset/wise-badgers-kick.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"openapi-typescript": patch
3+
---
4+
5+
support nested path parameters in --path-params-as-types

packages/openapi-typescript/src/transform/paths-object.ts

+21-7
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,36 @@
1-
import type { GlobalContext, PathsObject } from "../types.js";
1+
import type { GlobalContext, PathsObject, PathItemObject, ParameterObject, ReferenceObject, OperationObject } from "../types.js";
22
import { escStr, getEntries, indent } from "../utils.js";
33
import transformParameterObject from "./parameter-object.js";
44
import transformPathItemObject from "./path-item-object.js";
55

6+
const OPERATIONS = ["get", "post", "put", "delete", "options", "head", "patch", "trace"];
7+
8+
function extractPathParams(obj?: ReferenceObject | PathItemObject | OperationObject | undefined): Map<string, ParameterObject> {
9+
const params = new Map();
10+
obj &&
11+
"parameters" in obj &&
12+
obj.parameters?.forEach((p) => {
13+
if ("in" in p && p.in === "path") {
14+
params.set(p.name, p);
15+
}
16+
});
17+
return params;
18+
}
19+
620
export default function transformPathsObject(pathsObject: PathsObject, ctx: GlobalContext): string {
721
let { indentLv } = ctx;
822
const output: string[] = ["{"];
923
indentLv++;
1024
for (const [url, pathItemObject] of getEntries(pathsObject, ctx.alphabetize, ctx.excludeDeprecated)) {
1125
let path = url;
1226

27+
const pathParams = new Map([...extractPathParams(pathItemObject), ...OPERATIONS.flatMap((op) => Array.from(extractPathParams(pathItemObject[op as keyof PathItemObject])))]);
28+
1329
// build dynamic string template literal index
14-
if (ctx.pathParamsAsTypes && pathItemObject.parameters) {
15-
for (const p of pathItemObject.parameters) {
16-
if ("in" in p && p.in === "path") {
17-
const paramType = transformParameterObject(p, { path: `#/paths/${url}/parameters/${p.name}`, ctx });
18-
path = path.replace(`{${p.name}}`, `\${${paramType}}`);
19-
}
30+
if (ctx.pathParamsAsTypes && pathParams) {
31+
for (const p of pathParams.values()) {
32+
const paramType = transformParameterObject(p, { path: `#/paths/${url}/parameters/${p.name}`, ctx });
33+
path = path.replace(`{${p.name}}`, `\${${paramType}}`);
2034
}
2135
path = `[path: \`${path}\`]`;
2236
} else {

packages/openapi-typescript/test/index.test.ts

+81
Original file line numberDiff line numberDiff line change
@@ -714,6 +714,87 @@ export type components = Record<string, never>;
714714
715715
export type external = Record<string, never>;
716716
717+
export type operations = Record<string, never>;
718+
`);
719+
});
720+
});
721+
722+
describe("pathParamsAsTypes (with nested parameters)", () => {
723+
const schema: OpenAPI3 = {
724+
openapi: "3.1",
725+
info: { title: "Test", version: "1.0" },
726+
paths: {
727+
"/user/{user_id}": {
728+
get: {
729+
parameters: [{ name: "user_id", in: "path" }],
730+
},
731+
put: {
732+
parameters: [{ name: "user_id", in: "path" }],
733+
}
734+
},
735+
},
736+
};
737+
738+
test("false", async () => {
739+
const generated = await openapiTS(schema, { pathParamsAsTypes: false });
740+
expect(generated).toBe(`${BOILERPLATE}
741+
export interface paths {
742+
"/user/{user_id}": {
743+
get: {
744+
parameters: {
745+
path: {
746+
user_id: string;
747+
};
748+
};
749+
};
750+
put: {
751+
parameters: {
752+
path: {
753+
user_id: string;
754+
};
755+
};
756+
};
757+
};
758+
}
759+
760+
export type webhooks = Record<string, never>;
761+
762+
export type components = Record<string, never>;
763+
764+
export type external = Record<string, never>;
765+
766+
export type operations = Record<string, never>;
767+
`);
768+
});
769+
770+
test("true", async () => {
771+
const generated = await openapiTS(schema, { pathParamsAsTypes: true });
772+
expect(generated).toBe(`${BOILERPLATE}
773+
export interface paths {
774+
[path: \`/user/\${string}\`]: {
775+
get: {
776+
parameters: {
777+
path: {
778+
user_id: string;
779+
};
780+
};
781+
};
782+
put: {
783+
parameters: {
784+
path: {
785+
user_id: string;
786+
};
787+
};
788+
};
789+
};
790+
}
791+
792+
export type webhooks = Record<string, never>;
793+
794+
export type components = Record<string, never>;
795+
796+
export type external = Record<string, never>;
797+
717798
export type operations = Record<string, never>;
718799
`);
719800
});

0 commit comments

Comments
 (0)