From 40efa1828b155fd820f5328ea8fb3e8961f779f9 Mon Sep 17 00:00:00 2001 From: dding-g Date: Wed, 26 Mar 2025 16:14:18 +0900 Subject: [PATCH 1/3] fix: --union-enums option names fix to --generate-union-enums --- README.md | 2 +- index.ts | 5 - .../__snapshots__/basic.test.ts.snap | 243 ++++++++++++++++++ tests/spec/unionEnums/basic.test.ts | 2 +- 4 files changed, 245 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 3571268d..1116822a 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ Options: some swagger schemas use "default" response status code as success response type by default. (default: false) -r, --responses generate additional information about request responses also add typings for bad responses (default: false) - --union-enums generate all "enum" types as union types (T1 | T2 | TN) (default: false) + --generate-union-enums generate all "enum" types as union types (T1 | T2 | TN) (default: false) --add-readonly generate readonly properties (default: false) --route-types generate type definitions for API routes (default: false) --[no-]client generate an API class (default: true) diff --git a/index.ts b/index.ts index c80c1719..182db548 100644 --- a/index.ts +++ b/index.ts @@ -281,11 +281,6 @@ const generateCommand = defineCommand({ description: "data contract name suffix", default: codeGenBaseConfig.typeSuffix, }, - "union-enums": { - type: "boolean", - description: 'generate all "enum" types as union types (T1 | T2 | TN)', - default: codeGenBaseConfig.generateUnionEnums, - }, "unwrap-response-data": { type: "boolean", description: "unwrap the data item from the response", diff --git a/tests/spec/unionEnums/__snapshots__/basic.test.ts.snap b/tests/spec/unionEnums/__snapshots__/basic.test.ts.snap index 81d3498d..8185ecd3 100644 --- a/tests/spec/unionEnums/__snapshots__/basic.test.ts.snap +++ b/tests/spec/unionEnums/__snapshots__/basic.test.ts.snap @@ -1,5 +1,248 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html +exports[`basic > --generate-union-enums 1`] = ` +"/* eslint-disable */ +/* tslint:disable */ +// @ts-nocheck +/* + * --------------------------------------------------------------- + * ## THIS FILE WAS GENERATED VIA SWAGGER-TYPESCRIPT-API ## + * ## ## + * ## AUTHOR: acacode ## + * ## SOURCE: https://github.com/acacode/swagger-typescript-api ## + * --------------------------------------------------------------- + */ + +export type StringEnum = "String1" | "String2" | "String3" | "String4"; + +export type NumberEnum = 1 | 2 | 3 | 4; + +export type BooleanEnum = true | false; + +/** + * FooBar + * @format int32 + */ +export type IntEnumWithNames = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9; + +export type QueryParamsType = Record; +export type ResponseFormat = keyof Omit; + +export interface FullRequestParams extends Omit { + /** set parameter to \`true\` for call \`securityWorker\` for this request */ + secure?: boolean; + /** request path */ + path: string; + /** content type of request body */ + type?: ContentType; + /** query params */ + query?: QueryParamsType; + /** format of response (i.e. response.json() -> format: "json") */ + format?: ResponseFormat; + /** request body */ + body?: unknown; + /** base url */ + baseUrl?: string; + /** request cancellation token */ + cancelToken?: CancelToken; +} + +export type RequestParams = Omit; + +export interface ApiConfig { + baseUrl?: string; + baseApiParams?: Omit; + securityWorker?: (securityData: SecurityDataType | null) => Promise | RequestParams | void; + customFetch?: typeof fetch; +} + +export interface HttpResponse extends Response { + data: D; + error: E; +} + +type CancelToken = Symbol | string | number; + +export enum ContentType { + Json = "application/json", + FormData = "multipart/form-data", + UrlEncoded = "application/x-www-form-urlencoded", + Text = "text/plain", +} + +export class HttpClient { + public baseUrl: string = "http://localhost:8080/api/v1"; + private securityData: SecurityDataType | null = null; + private securityWorker?: ApiConfig["securityWorker"]; + private abortControllers = new Map(); + private customFetch = (...fetchParams: Parameters) => fetch(...fetchParams); + + private baseApiParams: RequestParams = { + credentials: "same-origin", + headers: {}, + redirect: "follow", + referrerPolicy: "no-referrer", + }; + + constructor(apiConfig: ApiConfig = {}) { + Object.assign(this, apiConfig); + } + + public setSecurityData = (data: SecurityDataType | null) => { + this.securityData = data; + }; + + protected encodeQueryParam(key: string, value: any) { + const encodedKey = encodeURIComponent(key); + return \`\${encodedKey}=\${encodeURIComponent(typeof value === "number" ? value : \`\${value}\`)}\`; + } + + protected addQueryParam(query: QueryParamsType, key: string) { + return this.encodeQueryParam(key, query[key]); + } + + protected addArrayQueryParam(query: QueryParamsType, key: string) { + const value = query[key]; + return value.map((v: any) => this.encodeQueryParam(key, v)).join("&"); + } + + protected toQueryString(rawQuery?: QueryParamsType): string { + const query = rawQuery || {}; + const keys = Object.keys(query).filter((key) => "undefined" !== typeof query[key]); + return keys + .map((key) => (Array.isArray(query[key]) ? this.addArrayQueryParam(query, key) : this.addQueryParam(query, key))) + .join("&"); + } + + protected addQueryParams(rawQuery?: QueryParamsType): string { + const queryString = this.toQueryString(rawQuery); + return queryString ? \`?\${queryString}\` : ""; + } + + private contentFormatters: Record any> = { + [ContentType.Json]: (input: any) => + input !== null && (typeof input === "object" || typeof input === "string") ? JSON.stringify(input) : input, + [ContentType.Text]: (input: any) => (input !== null && typeof input !== "string" ? JSON.stringify(input) : input), + [ContentType.FormData]: (input: any) => + Object.keys(input || {}).reduce((formData, key) => { + const property = input[key]; + formData.append( + key, + property instanceof Blob + ? property + : typeof property === "object" && property !== null + ? JSON.stringify(property) + : \`\${property}\`, + ); + return formData; + }, new FormData()), + [ContentType.UrlEncoded]: (input: any) => this.toQueryString(input), + }; + + protected mergeRequestParams(params1: RequestParams, params2?: RequestParams): RequestParams { + return { + ...this.baseApiParams, + ...params1, + ...(params2 || {}), + headers: { + ...(this.baseApiParams.headers || {}), + ...(params1.headers || {}), + ...((params2 && params2.headers) || {}), + }, + }; + } + + protected createAbortSignal = (cancelToken: CancelToken): AbortSignal | undefined => { + if (this.abortControllers.has(cancelToken)) { + const abortController = this.abortControllers.get(cancelToken); + if (abortController) { + return abortController.signal; + } + return void 0; + } + + const abortController = new AbortController(); + this.abortControllers.set(cancelToken, abortController); + return abortController.signal; + }; + + public abortRequest = (cancelToken: CancelToken) => { + const abortController = this.abortControllers.get(cancelToken); + + if (abortController) { + abortController.abort(); + this.abortControllers.delete(cancelToken); + } + }; + + public request = async ({ + body, + secure, + path, + type, + query, + format, + baseUrl, + cancelToken, + ...params + }: FullRequestParams): Promise> => { + const secureParams = + ((typeof secure === "boolean" ? secure : this.baseApiParams.secure) && + this.securityWorker && + (await this.securityWorker(this.securityData))) || + {}; + const requestParams = this.mergeRequestParams(params, secureParams); + const queryString = query && this.toQueryString(query); + const payloadFormatter = this.contentFormatters[type || ContentType.Json]; + const responseFormat = format || requestParams.format; + + return this.customFetch(\`\${baseUrl || this.baseUrl || ""}\${path}\${queryString ? \`?\${queryString}\` : ""}\`, { + ...requestParams, + headers: { + ...(requestParams.headers || {}), + ...(type && type !== ContentType.FormData ? { "Content-Type": type } : {}), + }, + signal: (cancelToken ? this.createAbortSignal(cancelToken) : requestParams.signal) || null, + body: typeof body === "undefined" || body === null ? null : payloadFormatter(body), + }).then(async (response) => { + const r = response.clone() as HttpResponse; + r.data = null as unknown as T; + r.error = null as unknown as E; + + const data = !responseFormat + ? r + : await response[responseFormat]() + .then((data) => { + if (r.ok) { + r.data = data; + } else { + r.error = data; + } + return r; + }) + .catch((e) => { + r.error = e; + return r; + }); + + if (cancelToken) { + this.abortControllers.delete(cancelToken); + } + + if (!response.ok) throw data; + return data; + }); + }; +} + +/** + * @title No title + * @baseUrl http://localhost:8080/api/v1 + */ +export class Api extends HttpClient {} +" +`; + exports[`basic > --union-enums 1`] = ` "/* eslint-disable */ /* tslint:disable */ diff --git a/tests/spec/unionEnums/basic.test.ts b/tests/spec/unionEnums/basic.test.ts index 6f238325..9a85c35a 100644 --- a/tests/spec/unionEnums/basic.test.ts +++ b/tests/spec/unionEnums/basic.test.ts @@ -17,7 +17,7 @@ describe("basic", async () => { await fs.rm(tmpdir, { recursive: true }); }); - test("--union-enums", async () => { + test("--generate-union-enums", async () => { await generateApi({ fileName: "schema", input: path.resolve(import.meta.dirname, "schema.json"), From 8c9706eaf2b51b396cecbca1dd0d1e2203148914 Mon Sep 17 00:00:00 2001 From: Sora Morimoto Date: Wed, 26 Mar 2025 20:35:58 +0900 Subject: [PATCH 2/3] Update vitest snapshot Signed-off-by: Sora Morimoto --- .../__snapshots__/basic.test.ts.snap | 243 ------------------ 1 file changed, 243 deletions(-) diff --git a/tests/spec/unionEnums/__snapshots__/basic.test.ts.snap b/tests/spec/unionEnums/__snapshots__/basic.test.ts.snap index 8185ecd3..0c61c1f0 100644 --- a/tests/spec/unionEnums/__snapshots__/basic.test.ts.snap +++ b/tests/spec/unionEnums/__snapshots__/basic.test.ts.snap @@ -242,246 +242,3 @@ export class HttpClient { export class Api extends HttpClient {} " `; - -exports[`basic > --union-enums 1`] = ` -"/* eslint-disable */ -/* tslint:disable */ -// @ts-nocheck -/* - * --------------------------------------------------------------- - * ## THIS FILE WAS GENERATED VIA SWAGGER-TYPESCRIPT-API ## - * ## ## - * ## AUTHOR: acacode ## - * ## SOURCE: https://github.com/acacode/swagger-typescript-api ## - * --------------------------------------------------------------- - */ - -export type StringEnum = "String1" | "String2" | "String3" | "String4"; - -export type NumberEnum = 1 | 2 | 3 | 4; - -export type BooleanEnum = true | false; - -/** - * FooBar - * @format int32 - */ -export type IntEnumWithNames = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9; - -export type QueryParamsType = Record; -export type ResponseFormat = keyof Omit; - -export interface FullRequestParams extends Omit { - /** set parameter to \`true\` for call \`securityWorker\` for this request */ - secure?: boolean; - /** request path */ - path: string; - /** content type of request body */ - type?: ContentType; - /** query params */ - query?: QueryParamsType; - /** format of response (i.e. response.json() -> format: "json") */ - format?: ResponseFormat; - /** request body */ - body?: unknown; - /** base url */ - baseUrl?: string; - /** request cancellation token */ - cancelToken?: CancelToken; -} - -export type RequestParams = Omit; - -export interface ApiConfig { - baseUrl?: string; - baseApiParams?: Omit; - securityWorker?: (securityData: SecurityDataType | null) => Promise | RequestParams | void; - customFetch?: typeof fetch; -} - -export interface HttpResponse extends Response { - data: D; - error: E; -} - -type CancelToken = Symbol | string | number; - -export enum ContentType { - Json = "application/json", - FormData = "multipart/form-data", - UrlEncoded = "application/x-www-form-urlencoded", - Text = "text/plain", -} - -export class HttpClient { - public baseUrl: string = "http://localhost:8080/api/v1"; - private securityData: SecurityDataType | null = null; - private securityWorker?: ApiConfig["securityWorker"]; - private abortControllers = new Map(); - private customFetch = (...fetchParams: Parameters) => fetch(...fetchParams); - - private baseApiParams: RequestParams = { - credentials: "same-origin", - headers: {}, - redirect: "follow", - referrerPolicy: "no-referrer", - }; - - constructor(apiConfig: ApiConfig = {}) { - Object.assign(this, apiConfig); - } - - public setSecurityData = (data: SecurityDataType | null) => { - this.securityData = data; - }; - - protected encodeQueryParam(key: string, value: any) { - const encodedKey = encodeURIComponent(key); - return \`\${encodedKey}=\${encodeURIComponent(typeof value === "number" ? value : \`\${value}\`)}\`; - } - - protected addQueryParam(query: QueryParamsType, key: string) { - return this.encodeQueryParam(key, query[key]); - } - - protected addArrayQueryParam(query: QueryParamsType, key: string) { - const value = query[key]; - return value.map((v: any) => this.encodeQueryParam(key, v)).join("&"); - } - - protected toQueryString(rawQuery?: QueryParamsType): string { - const query = rawQuery || {}; - const keys = Object.keys(query).filter((key) => "undefined" !== typeof query[key]); - return keys - .map((key) => (Array.isArray(query[key]) ? this.addArrayQueryParam(query, key) : this.addQueryParam(query, key))) - .join("&"); - } - - protected addQueryParams(rawQuery?: QueryParamsType): string { - const queryString = this.toQueryString(rawQuery); - return queryString ? \`?\${queryString}\` : ""; - } - - private contentFormatters: Record any> = { - [ContentType.Json]: (input: any) => - input !== null && (typeof input === "object" || typeof input === "string") ? JSON.stringify(input) : input, - [ContentType.Text]: (input: any) => (input !== null && typeof input !== "string" ? JSON.stringify(input) : input), - [ContentType.FormData]: (input: any) => - Object.keys(input || {}).reduce((formData, key) => { - const property = input[key]; - formData.append( - key, - property instanceof Blob - ? property - : typeof property === "object" && property !== null - ? JSON.stringify(property) - : \`\${property}\`, - ); - return formData; - }, new FormData()), - [ContentType.UrlEncoded]: (input: any) => this.toQueryString(input), - }; - - protected mergeRequestParams(params1: RequestParams, params2?: RequestParams): RequestParams { - return { - ...this.baseApiParams, - ...params1, - ...(params2 || {}), - headers: { - ...(this.baseApiParams.headers || {}), - ...(params1.headers || {}), - ...((params2 && params2.headers) || {}), - }, - }; - } - - protected createAbortSignal = (cancelToken: CancelToken): AbortSignal | undefined => { - if (this.abortControllers.has(cancelToken)) { - const abortController = this.abortControllers.get(cancelToken); - if (abortController) { - return abortController.signal; - } - return void 0; - } - - const abortController = new AbortController(); - this.abortControllers.set(cancelToken, abortController); - return abortController.signal; - }; - - public abortRequest = (cancelToken: CancelToken) => { - const abortController = this.abortControllers.get(cancelToken); - - if (abortController) { - abortController.abort(); - this.abortControllers.delete(cancelToken); - } - }; - - public request = async ({ - body, - secure, - path, - type, - query, - format, - baseUrl, - cancelToken, - ...params - }: FullRequestParams): Promise> => { - const secureParams = - ((typeof secure === "boolean" ? secure : this.baseApiParams.secure) && - this.securityWorker && - (await this.securityWorker(this.securityData))) || - {}; - const requestParams = this.mergeRequestParams(params, secureParams); - const queryString = query && this.toQueryString(query); - const payloadFormatter = this.contentFormatters[type || ContentType.Json]; - const responseFormat = format || requestParams.format; - - return this.customFetch(\`\${baseUrl || this.baseUrl || ""}\${path}\${queryString ? \`?\${queryString}\` : ""}\`, { - ...requestParams, - headers: { - ...(requestParams.headers || {}), - ...(type && type !== ContentType.FormData ? { "Content-Type": type } : {}), - }, - signal: (cancelToken ? this.createAbortSignal(cancelToken) : requestParams.signal) || null, - body: typeof body === "undefined" || body === null ? null : payloadFormatter(body), - }).then(async (response) => { - const r = response.clone() as HttpResponse; - r.data = null as unknown as T; - r.error = null as unknown as E; - - const data = !responseFormat - ? r - : await response[responseFormat]() - .then((data) => { - if (r.ok) { - r.data = data; - } else { - r.error = data; - } - return r; - }) - .catch((e) => { - r.error = e; - return r; - }); - - if (cancelToken) { - this.abortControllers.delete(cancelToken); - } - - if (!response.ok) throw data; - return data; - }); - }; -} - -/** - * @title No title - * @baseUrl http://localhost:8080/api/v1 - */ -export class Api extends HttpClient {} -" -`; From 6a74f6ea1409f9e88fde0f82f7365b537fafbf64 Mon Sep 17 00:00:00 2001 From: Sora Morimoto Date: Wed, 26 Mar 2025 20:38:51 +0900 Subject: [PATCH 3/3] Add changeset Signed-off-by: Sora Morimoto --- .changeset/weak-lemons-buy.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/weak-lemons-buy.md diff --git a/.changeset/weak-lemons-buy.md b/.changeset/weak-lemons-buy.md new file mode 100644 index 00000000..b8904048 --- /dev/null +++ b/.changeset/weak-lemons-buy.md @@ -0,0 +1,5 @@ +--- +"swagger-typescript-api": patch +--- + +Remove redundant `union-enums` option in favor of `generate-union-enums`.