From ed0ab4739bc849c1d239e47bd97ae10a890bafad Mon Sep 17 00:00:00 2001 From: barak_alon Date: Mon, 22 May 2023 20:24:37 -0700 Subject: [PATCH] fix: support nested path parameters in --path-params-as-types --- .changeset/wise-badgers-kick.md | 5 ++ .../src/transform/paths-object.ts | 28 +++++-- .../openapi-typescript/test/index.test.ts | 81 +++++++++++++++++++ 3 files changed, 107 insertions(+), 7 deletions(-) create mode 100644 .changeset/wise-badgers-kick.md diff --git a/.changeset/wise-badgers-kick.md b/.changeset/wise-badgers-kick.md new file mode 100644 index 000000000..a3b96dbf3 --- /dev/null +++ b/.changeset/wise-badgers-kick.md @@ -0,0 +1,5 @@ +--- +"openapi-typescript": patch +--- + +support nested path parameters in --path-params-as-types diff --git a/packages/openapi-typescript/src/transform/paths-object.ts b/packages/openapi-typescript/src/transform/paths-object.ts index 7f14144a4..f4dd3cd1c 100644 --- a/packages/openapi-typescript/src/transform/paths-object.ts +++ b/packages/openapi-typescript/src/transform/paths-object.ts @@ -1,8 +1,22 @@ -import type { GlobalContext, PathsObject } from "../types.js"; +import type { GlobalContext, PathsObject, PathItemObject, ParameterObject, ReferenceObject, OperationObject } from "../types.js"; import { escStr, getEntries, indent } from "../utils.js"; import transformParameterObject from "./parameter-object.js"; import transformPathItemObject from "./path-item-object.js"; +const OPERATIONS = ["get", "post", "put", "delete", "options", "head", "patch", "trace"]; + +function extractPathParams(obj?: ReferenceObject | PathItemObject | OperationObject | undefined): Map { + const params = new Map(); + obj && + "parameters" in obj && + obj.parameters?.forEach((p) => { + if ("in" in p && p.in === "path") { + params.set(p.name, p); + } + }); + return params; +} + export default function transformPathsObject(pathsObject: PathsObject, ctx: GlobalContext): string { let { indentLv } = ctx; const output: string[] = ["{"]; @@ -10,13 +24,13 @@ export default function transformPathsObject(pathsObject: PathsObject, ctx: Glob for (const [url, pathItemObject] of getEntries(pathsObject, ctx.alphabetize, ctx.excludeDeprecated)) { let path = url; + const pathParams = new Map([...extractPathParams(pathItemObject), ...OPERATIONS.flatMap((op) => Array.from(extractPathParams(pathItemObject[op as keyof PathItemObject])))]); + // build dynamic string template literal index - if (ctx.pathParamsAsTypes && pathItemObject.parameters) { - for (const p of pathItemObject.parameters) { - if ("in" in p && p.in === "path") { - const paramType = transformParameterObject(p, { path: `#/paths/${url}/parameters/${p.name}`, ctx }); - path = path.replace(`{${p.name}}`, `\${${paramType}}`); - } + if (ctx.pathParamsAsTypes && pathParams) { + for (const p of pathParams.values()) { + const paramType = transformParameterObject(p, { path: `#/paths/${url}/parameters/${p.name}`, ctx }); + path = path.replace(`{${p.name}}`, `\${${paramType}}`); } path = `[path: \`${path}\`]`; } else { diff --git a/packages/openapi-typescript/test/index.test.ts b/packages/openapi-typescript/test/index.test.ts index 93d2b8dbf..629978677 100644 --- a/packages/openapi-typescript/test/index.test.ts +++ b/packages/openapi-typescript/test/index.test.ts @@ -714,6 +714,87 @@ export type components = Record; export type external = Record; +export type operations = Record; +`); + }); + }); + + describe("pathParamsAsTypes (with nested parameters)", () => { + const schema: OpenAPI3 = { + openapi: "3.1", + info: { title: "Test", version: "1.0" }, + paths: { + "/user/{user_id}": { + get: { + parameters: [{ name: "user_id", in: "path" }], + }, + put: { + parameters: [{ name: "user_id", in: "path" }], + } + }, + }, + }; + + test("false", async () => { + const generated = await openapiTS(schema, { pathParamsAsTypes: false }); + expect(generated).toBe(`${BOILERPLATE} +export interface paths { + "/user/{user_id}": { + get: { + parameters: { + path: { + user_id: string; + }; + }; + }; + put: { + parameters: { + path: { + user_id: string; + }; + }; + }; + }; +} + +export type webhooks = Record; + +export type components = Record; + +export type external = Record; + +export type operations = Record; +`); + }); + + test("true", async () => { + const generated = await openapiTS(schema, { pathParamsAsTypes: true }); + expect(generated).toBe(`${BOILERPLATE} +export interface paths { + [path: \`/user/\${string}\`]: { + get: { + parameters: { + path: { + user_id: string; + }; + }; + }; + put: { + parameters: { + path: { + user_id: string; + }; + }; + }; + }; +} + +export type webhooks = Record; + +export type components = Record; + +export type external = Record; + export type operations = Record; `); });