From a0720e44242a2c08d083d7ab57b5a5bed3082d60 Mon Sep 17 00:00:00 2001 From: liangskyli <640634387@qq.com> Date: Mon, 8 May 2023 13:57:33 +0800 Subject: [PATCH 1/4] fix: load yaml file transform ref bug --- src/load.ts | 4 + test/load.test.ts | 255 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 259 insertions(+) diff --git a/src/load.ts b/src/load.ts index 9adbb19cb..5b138dc9c 100644 --- a/src/load.ts +++ b/src/load.ts @@ -279,6 +279,10 @@ export default async function load( const ref = parseRef(node.$ref); + // when ref.path is [], $ref has been transform, skip transform + if (ref.path.length === 0) { + return; + } // local $ref: convert into TS path if (ref.filename === ".") { node.$ref = makeTSIndex(ref.path); diff --git a/test/load.test.ts b/test/load.test.ts index 11ac005dc..888f467d9 100644 --- a/test/load.test.ts +++ b/test/load.test.ts @@ -82,6 +82,261 @@ describe("Load", () => { }); expect(output["."].schema).toEqual(exampleSchema); }); + // Regression test for https://github.com/drwpow/openapi-typescript/issues/1093 + test("ref transform .json .yaml", async () => { + const exampleSchemaTransformTest = { + "openapi": "3.1.0", + "info": { + "title": "custom title", + "license": { + "name": "This file is auto generated, do not edit!" + }, + "version": "0.4.1" + }, + "paths": { + "/root/v4/getQueryParams1-v4": { + "get": { + "tags": ["Test4"], + "responses": { + "200": { + "description": "Success", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + }, + "parameters": [ + { + "name": "activityBases", + "in": "query", + "required": true, + "schema": { + "type": "array", + "items": { + "$ref": "components[\"schemas\"][\"ActivityBase\"]" + } + }, + "description": "activityBases" + } + ] + } + } + }, + "components": { + "schemas": { + "getQueryParams1Request": { + "type": "object", + "properties": { + "activityBases": { + "description": "activityBases", + "type": "array", + "items": { + "$ref": "components[\"schemas\"][\"ActivityBase\"]" + } + } + }, + "required": ["activityBases"] + }, + "ActivityBase": { + "allOf": [ + { + "type": "object", + "properties": { + "name": { + "type": "string" + } + }, + "required": ["name"] + }, + { + "type": "object", + "properties": { + "id": { + "type": "number" + } + }, + "required": ["id"] + } + ] + } + } + }, + "tags": [ + { + "name": "Test4", + "description": "Test3Controller 注释\n注释3" + } + ] + }; + const output1 = await load(new URL("https://example.com/openapi.json"), { + async fetch() { + const jsonData = `{ + "openapi": "3.1.0", + "info": { + "title": "custom title", + "license": { + "name": "This file is auto generated, do not edit!" + }, + "version": "0.4.1" + }, + "paths": { + "/root/v4/getQueryParams1-v4": { + "get": { + "tags": ["Test4"], + "responses": { + "200": { + "description": "Success", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + }, + "parameters": [ + { + "name": "activityBases", + "in": "query", + "required": true, + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ActivityBase" + } + }, + "description": "activityBases" + } + ] + } + } + }, + "components": { + "schemas": { + "getQueryParams1Request": { + "type": "object", + "properties": { + "activityBases": { + "description": "activityBases", + "type": "array", + "items": { + "$ref": "#/components/schemas/ActivityBase" + } + } + }, + "required": ["activityBases"] + }, + "ActivityBase": { + "allOf": [ + { + "type": "object", + "properties": { + "name": { + "type": "string" + } + }, + "required": ["name"] + }, + { + "type": "object", + "properties": { + "id": { + "type": "number" + } + }, + "required": ["id"] + } + ] + } + } + }, + "tags": [ + { + "name": "Test4", + "description": "Test3Controller 注释\\n注释3" + } + ] +} +`; + return new Response(jsonData, { + headers: { "Content-Type": "text/plain" }, + }); + }, + }); + expect(output1["."].schema).toEqual(exampleSchemaTransformTest); + + const output2 = await load(new URL("https://example.com/openapi.yaml"), { + async fetch() { + const yamlData =`openapi: 3.1.0 +info: + title: custom title + license: + name: This file is auto generated, do not edit! + version: 0.4.1 +paths: + /root/v4/getQueryParams1-v4: + get: + tags: + - Test4 + responses: + '200': + description: Success + content: + text/plain: + schema: + type: string + parameters: + - name: activityBases + in: query + required: true + schema: + type: array + items: &ref_0 + $ref: '#/components/schemas/ActivityBase' + description: activityBases +components: + schemas: + getQueryParams1Request: + type: object + properties: + activityBases: + description: activityBases + type: array + items: *ref_0 + required: + - activityBases + ActivityBase: + allOf: + - type: object + properties: + name: + type: string + required: + - name + - type: object + properties: + id: + type: number + required: + - id +tags: + - name: Test4 + description: |- + Test3Controller 注释 + 注释3 +`; + return new Response(yamlData, { + headers: { "Content-Type": "text/plain" }, + }); + }, + }); + expect(output2["."].schema).toEqual(exampleSchemaTransformTest); + }); }); }); }); From 5d3aa6526273c5a3d881db021bec3b87030dcf57 Mon Sep 17 00:00:00 2001 From: liangskyli <640634387@qq.com> Date: Mon, 8 May 2023 14:46:26 +0800 Subject: [PATCH 2/4] fix: $ref has been transform determine --- packages/openapi-typescript/src/load.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/openapi-typescript/src/load.ts b/packages/openapi-typescript/src/load.ts index 5b138dc9c..ebbbed6c3 100644 --- a/packages/openapi-typescript/src/load.ts +++ b/packages/openapi-typescript/src/load.ts @@ -280,7 +280,7 @@ export default async function load( const ref = parseRef(node.$ref); // when ref.path is [], $ref has been transform, skip transform - if (ref.path.length === 0) { + if (ref.path.length === 0 && (ref.filename.startsWith("components[") || ref.filename.startsWith("external["))) { return; } // local $ref: convert into TS path From 1d0011ba226cc3fa1565b908070d117d5ac116ac Mon Sep 17 00:00:00 2001 From: liangskyli <640634387@qq.com> Date: Wed, 10 May 2023 09:07:22 +0800 Subject: [PATCH 3/4] fix: avoidance $ref has been transform many times --- packages/openapi-typescript/src/load.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/openapi-typescript/src/load.ts b/packages/openapi-typescript/src/load.ts index ea8172bc2..41141d893 100644 --- a/packages/openapi-typescript/src/load.ts +++ b/packages/openapi-typescript/src/load.ts @@ -181,6 +181,8 @@ export default async function load(schema: URL | Subschema | Readable, options: error(`Invalid schema`); process.exit(1); } + // avoidance $ref has been transform many times, use JSON.parse and JSON.stringify fix: https://github.com/drwpow/openapi-typescript/issues/1093 + options.schemas[schemaID].schema = JSON.parse(JSON.stringify(options.schemas[schemaID].schema)); // 2. resolve $refs const currentSchema = options.schemas[schemaID].schema; @@ -256,10 +258,6 @@ export default async function load(schema: URL | Subschema | Readable, options: const ref = parseRef(node.$ref); - // when ref.path is [], $ref has been transform, skip transform - if (ref.path.length === 0 && (ref.filename.startsWith("components[") || ref.filename.startsWith("external["))) { - return; - } // local $ref: convert into TS path if (ref.filename === ".") { node.$ref = makeTSIndex(ref.path); From f864d8161c982cef103df23a47b5eeb1a6599798 Mon Sep 17 00:00:00 2001 From: liangsky <640634387@qq.com> Date: Wed, 10 May 2023 21:58:44 +0800 Subject: [PATCH 4/4] chore: examples digital-ocean-api.ts update --- .../examples/digital-ocean-api.ts | 86 +++++++++---------- 1 file changed, 43 insertions(+), 43 deletions(-) diff --git a/packages/openapi-typescript/examples/digital-ocean-api.ts b/packages/openapi-typescript/examples/digital-ocean-api.ts index 092d4242f..566fe527e 100644 --- a/packages/openapi-typescript/examples/digital-ocean-api.ts +++ b/packages/openapi-typescript/examples/digital-ocean-api.ts @@ -932,13 +932,13 @@ export interface external { /** * Format: date-time * @description A time value given in ISO8601 combined date and time format that represents when the action was initiated. - * @example "2020-11-14T16:29:21.000Z" + * @example 2020-11-14T16:29:21.000Z */ started_at?: string; /** * Format: date-time * @description A time value given in ISO8601 combined date and time format that represents when the action was completed. - * @example "2020-11-14T16:30:06.000Z" + * @example 2020-11-14T16:30:06.000Z */ completed_at?: string | null; /** @@ -1317,7 +1317,7 @@ export interface external { query?: { /** * @description Optional day to query. Only the date component of the timestamp will be considered. Default: yesterday. - * @example "2023-01-17T00:00:00.000Z" + * @example 2023-01-17T00:00:00.000Z */ date?: string; }; @@ -1646,13 +1646,13 @@ export interface external { /** * The start time of this step * Format: date-time - * @example "2020-11-19T20:27:18.000Z" + * @example 2020-11-19T20:27:18.000Z */ started_at?: string; /** * The start time of this step * Format: date-time - * @example "2020-11-19T20:27:18.000Z" + * @example 2020-11-19T20:27:18.000Z */ ended_at?: string; reason?: external["resources/apps/models/app_alert_progress_step_reason.yml"]; @@ -2020,7 +2020,7 @@ export interface external { /** * Format: date-time * @description Optional day to query. Only the date component of the timestamp will be considered. Default: yesterday. - * @example "2023-01-17T00:00:00.000Z" + * @example 2023-01-17T00:00:00.000Z */ date?: string; } @@ -2030,7 +2030,7 @@ export interface external { /** * Format: date-time * @description The date for the metrics data. - * @example "2023-01-17T00:00:00.000Z" + * @example 2023-01-17T00:00:00.000Z */ date?: string; } @@ -2281,7 +2281,7 @@ export interface external { /** * The creation time of the app * Format: date-time - * @example "2020-11-19T20:27:18.000Z" + * @example 2020-11-19T20:27:18.000Z */ created_at?: string; /** @@ -2300,7 +2300,7 @@ export interface external { /** * The creation time of the last deployment * Format: date-time - * @example "2020-11-19T20:27:18.000Z" + * @example 2020-11-19T20:27:18.000Z */ last_deployment_created_at?: string; /** @@ -2339,7 +2339,7 @@ export interface external { /** * Time of the app's last configuration update * Format: date-time - * @example "2020-12-01T00:42:16.000Z" + * @example 2020-12-01T00:42:16.000Z */ updated_at?: string; pinned_deployment?: external["resources/apps/models/apps_deployment.yml"]; @@ -2479,7 +2479,7 @@ export interface external { /** * The end time of this step * Format: date-time - * @example "2020-11-19T20:27:18.000Z" + * @example 2020-11-19T20:27:18.000Z */ ended_at?: string; /** @@ -2499,7 +2499,7 @@ export interface external { /** * The start time of this step * Format: date-time - * @example "2020-11-19T20:27:18.000Z" + * @example 2020-11-19T20:27:18.000Z */ started_at?: string; status?: external["resources/apps/models/apps_deployment_progress_step_status.yml"]; @@ -2595,7 +2595,7 @@ export interface external { /** * The creation time of the deployment * Format: date-time - * @example "2020-07-28T18:00:00.000Z" + * @example 2020-07-28T18:00:00.000Z */ created_at?: string; /** @@ -2611,7 +2611,7 @@ export interface external { /** * When the deployment phase was last updated * Format: date-time - * @example "1901-01-01T00:00:00.000Z" + * @example 1901-01-01T00:00:00.000Z */ phase_last_updated_at?: string; progress?: external["resources/apps/models/apps_deployment_progress.yml"]; @@ -2628,7 +2628,7 @@ export interface external { /** * When the deployment was last updated * Format: date-time - * @example "2020-07-28T18:00:00.000Z" + * @example 2020-07-28T18:00:00.000Z */ updated_at?: string; /** Worker components that are part of this deployment */ @@ -3302,7 +3302,7 @@ export interface external { /** * Format: date-time * @description The time at which balances were most recently generated. - * @example "2019-07-09T15:01:12.000Z" + * @example 2019-07-09T15:01:12.000Z */ generated_at?: string; } @@ -3339,12 +3339,12 @@ export interface external { country_iso2_code?: string; /** * @description Timestamp billing address was created - * @example "2019-09-03T16:34:46.000Z" + * @example 2019-09-03T16:34:46.000Z */ created_at?: string; /** * @description Timestamp billing address was updated - * @example "2019-09-03T16:34:46.000Z" + * @example 2019-09-03T16:34:46.000Z */ updated_at?: string; } @@ -3372,7 +3372,7 @@ export interface external { /** * Format: date-time * @description Time the billing history entry occurred. - * @example "2018-06-01T08:44:38.000Z" + * @example 2018-06-01T08:44:38.000Z */ date?: string; /** @@ -3425,12 +3425,12 @@ export interface external { duration_unit?: string; /** * @description Time the invoice item began to be billed for usage. - * @example "2020-01-01T00:00:00.000Z" + * @example 2020-01-01T00:00:00.000Z */ start_time?: string; /** * @description Time the invoice item stopped being billed for usage. - * @example "2020-02-01T00:00:00.000Z" + * @example 2020-02-01T00:00:00.000Z */ end_time?: string; /** @@ -3457,7 +3457,7 @@ export interface external { invoice_period?: string; /** * @description Time the invoice was last updated. This is only included with the invoice preview. - * @example "2020-01-23T06:31:50.000Z" + * @example 2020-01-23T06:31:50.000Z */ updated_at?: string; } @@ -3981,7 +3981,7 @@ export interface external { /** * Format: date-time * @description A time value given in ISO8601 combined date and time format that represents the certificate's expiration date. - * @example "2017-02-22T00:23:00.000Z" + * @example 2017-02-22T00:23:00.000Z */ not_after?: string; /** @@ -3992,7 +3992,7 @@ export interface external { /** * Format: date-time * @description A time value given in ISO8601 combined date and time format that represents when the certificate was created. - * @example "2017-02-08T16:02:37.000Z" + * @example 2017-02-08T16:02:37.000Z */ created_at?: string; /** @@ -5179,7 +5179,7 @@ export interface external { /** * Format: date-time * @description A time value given in ISO8601 combined date and time format at which the backup was created. - * @example "2019-01-31T19:25:22.000Z" + * @example 2019-01-31T19:25:22.000Z */ created_at: string; /** @@ -5261,7 +5261,7 @@ export interface external { /** * Format: date-time * @description The timestamp of an existing database cluster backup in ISO8601 combined date and time format. The most recent backup will be used if excluded. - * @example "2019-01-31T19:25:22.000Z" + * @example 2019-01-31T19:25:22.000Z */ backup_created_at?: string; } @@ -5330,7 +5330,7 @@ export interface external { /** * Format: date-time * @description A time value given in ISO8601 combined date and time format that represents when the database cluster was created. - * @example "2019-01-11T18:37:36.000Z" + * @example 2019-01-11T18:37:36.000Z */ created_at?: string; /** @@ -5503,7 +5503,7 @@ export interface external { /** * Format: date-time * @description A time value given in ISO8601 combined date and time format that represents when the database cluster was created. - * @example "2019-01-11T18:37:36.000Z" + * @example 2019-01-11T18:37:36.000Z */ created_at?: string; /** @@ -5596,7 +5596,7 @@ export interface external { /** * Format: date-time * @description A time value given in ISO8601 combined date and time format that represents when the firewall rule was created. - * @example "2019-01-11T18:37:36.000Z" + * @example 2019-01-11T18:37:36.000Z */ created_at?: string; } @@ -5765,7 +5765,7 @@ export interface external { status?: "running" | "canceled" | "error" | "done"; /** * @description The time the migration was initiated, in ISO 8601 format. - * @example "2020-10-29T15:57:38.000Z" + * @example 2020-10-29T15:57:38.000Z */ created_at?: string; } @@ -7426,7 +7426,7 @@ export interface external { /** * Format: date-time * @description A time value given in ISO8601 combined date and time format indicating when the requested action was completed. - * @example "2020-04-01T18:11:49.000Z" + * @example 2020-04-01T18:11:49.000Z */ completed_at?: string; /** @@ -7466,7 +7466,7 @@ export interface external { /** * Format: date-time * @description A time value given in ISO8601 combined date and time format indicating when the resource was destroyed if the request was successful. - * @example "2020-04-01T18:11:49.000Z" + * @example 2020-04-01T18:11:49.000Z */ destroyed_at?: string; /** @@ -9124,12 +9124,12 @@ export interface external { namespace?: string; /** * @description UTC time string. - * @example "2022-09-14T04:16:45.000Z" + * @example 2022-09-14T04:16:45.000Z */ created_at?: string; /** * @description UTC time string. - * @example "2022-09-14T04:16:45.000Z" + * @example 2022-09-14T04:16:45.000Z */ updated_at?: string; /** @@ -9194,24 +9194,24 @@ export interface external { is_enabled?: boolean; /** * @description UTC time string. - * @example "2022-11-11T04:16:45.000Z" + * @example 2022-11-11T04:16:45.000Z */ created_at?: string; /** * @description UTC time string. - * @example "2022-11-11T04:16:45.000Z" + * @example 2022-11-11T04:16:45.000Z */ updated_at?: string; scheduled_details?: external["resources/functions/models/scheduled_details.yml"]; scheduled_runs?: { /** * @description Indicates last run time. null value indicates trigger not run yet. - * @example "2022-11-11T04:16:45.000Z" + * @example 2022-11-11T04:16:45.000Z */ last_run_at?: string | null; /** * @description Indicates next run time. null value indicates trigger will not run. - * @example "2022-11-11T04:16:45.000Z" + * @example 2022-11-11T04:16:45.000Z */ next_run_at?: string | null; }; @@ -9606,7 +9606,7 @@ export interface external { /** * Format: date-time * @description A time value given in ISO8601 combined date and time format that represents when the image was created. - * @example "2020-05-04T22:23:02.000Z" + * @example 2020-05-04T22:23:02.000Z */ created_at?: string; /** @@ -10667,7 +10667,7 @@ export interface external { /** * Format: date-time * @description A time value given in ISO8601 combined date and time format that represents when the access token expires. - * @example "2019-11-09T11:50:28.889Z" + * @example 2019-11-09T11:50:28.889Z */ expires_at?: string; } @@ -12587,13 +12587,13 @@ export interface external { /** * Format: date-time * @description The time the garbage collection was created. - * @example "2020-10-30T21:03:24.000Z" + * @example 2020-10-30T21:03:24.000Z */ created_at?: string; /** * Format: date-time * @description The time the garbage collection was last updated. - * @example "2020-10-30T21:03:44.000Z" + * @example 2020-10-30T21:03:44.000Z */ updated_at?: string; /** @@ -14774,7 +14774,7 @@ export interface external { size_gigabytes?: number; /** * @description A time value given in ISO8601 combined date and time format that represents when the block storage volume was created. - * @example "2020-03-02T17:00:49.000Z" + * @example 2020-03-02T17:00:49.000Z */ created_at?: string; tags?: external["shared/attributes/tags_array.yml"];