From 884b14b8ad910d4f069dad7b740a5e737ec4504c Mon Sep 17 00:00:00 2001 From: Valentin Agachi Date: Thu, 4 Jul 2024 11:26:04 +0200 Subject: [PATCH] fix: Generate valid types for referenced nested properties --- .changeset/plenty-islands-tie.md | 5 ++ packages/openapi-typescript/src/lib/ts.ts | 5 ++ .../openapi-typescript/test/lib/ts.test.ts | 10 ++++ .../test/transform/components-object.test.ts | 54 +++++++++++++++++++ 4 files changed, 74 insertions(+) create mode 100644 .changeset/plenty-islands-tie.md diff --git a/.changeset/plenty-islands-tie.md b/.changeset/plenty-islands-tie.md new file mode 100644 index 000000000..384301cbe --- /dev/null +++ b/.changeset/plenty-islands-tie.md @@ -0,0 +1,5 @@ +--- +"openapi-typescript": patch +--- + +Generate valid types for referenced nested properties diff --git a/packages/openapi-typescript/src/lib/ts.ts b/packages/openapi-typescript/src/lib/ts.ts index e83202f87..f0c3f9313 100644 --- a/packages/openapi-typescript/src/lib/ts.ts +++ b/packages/openapi-typescript/src/lib/ts.ts @@ -122,6 +122,11 @@ export function oapiRef(path: string): ts.TypeNode { ); if (pointer.length > 1) { for (let i = 1; i < pointer.length; i++) { + // Skip `properties` items when in the middle of the pointer + // See: https://github.com/openapi-ts/openapi-typescript/issues/1742 + if (i > 2 && i < pointer.length - 1 && pointer[i] === "properties") { + continue; + } t = ts.factory.createIndexedAccessTypeNode( t, ts.factory.createLiteralTypeNode( diff --git a/packages/openapi-typescript/test/lib/ts.test.ts b/packages/openapi-typescript/test/lib/ts.test.ts index 031a49c39..517a00d58 100644 --- a/packages/openapi-typescript/test/lib/ts.test.ts +++ b/packages/openapi-typescript/test/lib/ts.test.ts @@ -64,6 +64,16 @@ describe("oapiRef", () => { test("multiple parts", () => { expect(astToString(oapiRef("#/components/schemas/User")).trim()).toBe(`components["schemas"]["User"]`); }); + + test("removes inner `properties`", () => { + expect(astToString(oapiRef("#/components/schemas/User/properties/username")).trim()).toBe( + `components["schemas"]["User"]["username"]`, + ); + }); + + test("leaves final `properties` intact", () => { + expect(astToString(oapiRef("#/components/schemas/properties")).trim()).toBe(`components["schemas"]["properties"]`); + }); }); describe("tsEnum", () => { diff --git a/packages/openapi-typescript/test/transform/components-object.test.ts b/packages/openapi-typescript/test/transform/components-object.test.ts index 2c7a93c22..2d7989aa0 100644 --- a/packages/openapi-typescript/test/transform/components-object.test.ts +++ b/packages/openapi-typescript/test/transform/components-object.test.ts @@ -503,6 +503,60 @@ describe("transformComponentsObject", () => { }, }, ], + [ + "$ref nested properties", + { + given: { + parameters: { + direct: { + name: "direct", + in: "query", + required: true, + schema: { + $ref: "#/components/aaa", + }, + }, + nested: { + name: "nested", + in: "query", + required: true, + schema: { + $ref: "#/components/schemas/bbb/properties/ccc", + }, + }, + }, + schemas: { + aaa: { + type: "string", + }, + bbb: { + type: "object", + properties: { + ccc: { + type: "string", + }, + }, + }, + }, + }, + want: `{ + schemas: { + aaa: string; + bbb: { + ccc?: string; + }; + }; + responses: never; + parameters: { + direct: components["aaa"]; + nested: components["schemas"]["bbb"]["ccc"]; + }; + requestBodies: never; + headers: never; + pathItems: never; +}`, + }, + ], ]; for (const [testName, { given, want, options = DEFAULT_OPTIONS, ci }] of tests) {