diff --git a/.changeset/ninety-windows-accept.md b/.changeset/ninety-windows-accept.md new file mode 100644 index 000000000..2222e9c13 --- /dev/null +++ b/.changeset/ninety-windows-accept.md @@ -0,0 +1,5 @@ +--- +'@hey-api/openapi-ts': patch +--- + +fix: handle tree-shakeable angular client case diff --git a/packages/openapi-ts/src/compiler/index.ts b/packages/openapi-ts/src/compiler/index.ts index f87fe5aca..0c585c8f3 100644 --- a/packages/openapi-ts/src/compiler/index.ts +++ b/packages/openapi-ts/src/compiler/index.ts @@ -168,6 +168,7 @@ export class TypeScriptFile { } export const compiler = { + anonymousFunction: types.createAnonymousFunction, arrayLiteralExpression: types.createArrayLiteralExpression, arrowFunction: types.createArrowFunction, awaitExpression: types.createAwaitExpression, diff --git a/packages/openapi-ts/src/compiler/types.ts b/packages/openapi-ts/src/compiler/types.ts index 9739bc62e..f78cbc663 100644 --- a/packages/openapi-ts/src/compiler/types.ts +++ b/packages/openapi-ts/src/compiler/types.ts @@ -298,6 +298,44 @@ export const createArrowFunction = ({ return expression; }; +/** + * Create anonymous function type expression. + */ +export const createAnonymousFunction = ({ + async, + comment, + multiLine, + parameters = [], + returnType, + statements = [], + types = [], +}: { + async?: boolean; + comment?: Comments; + multiLine?: boolean; + parameters?: FunctionParameter[]; + returnType?: string | ts.TypeNode; + statements?: ts.Statement[]; + types?: FunctionTypeParameter[]; +}) => { + const expression = ts.factory.createFunctionExpression( + async ? [ts.factory.createModifier(ts.SyntaxKind.AsyncKeyword)] : undefined, + undefined, + undefined, + types ? toTypeParameters(types) : undefined, + toParameterDeclarations(parameters), + returnType ? createTypeNode(returnType) : undefined, + ts.factory.createBlock(statements, multiLine), + ); + + addLeadingComments({ + comments: comment, + node: expression, + }); + + return expression; +}; + /** * Create Array type expression. * @param arr - The array to create. diff --git a/packages/openapi-ts/src/generate/services.ts b/packages/openapi-ts/src/generate/services.ts index 68feb7757..92505bfbf 100644 --- a/packages/openapi-ts/src/generate/services.ts +++ b/packages/openapi-ts/src/generate/services.ts @@ -610,7 +610,7 @@ const processService = ({ if (!config.services.asClass && !config.name) { for (const operation of service.operations) { - const expression = compiler.arrowFunction({ + const compileFunctionParams = { parameters: toOperationParamType(client, operation), returnType: isStandalone ? undefined @@ -622,7 +622,11 @@ const processService = ({ onClientImport, ), types: isStandalone ? [throwOnErrorTypeGeneric] : undefined, - }); + }; + const expression = + config.client.name === 'angular' + ? compiler.anonymousFunction(compileFunctionParams) + : compiler.arrowFunction(compileFunctionParams); const statement = compiler.constVariable({ comment: toOperationComment(operation), exportConst: true, diff --git a/packages/openapi-ts/test/__snapshots__/test/generated/v3_angular_tree_shakeable/core/ApiError.ts.snap b/packages/openapi-ts/test/__snapshots__/test/generated/v3_angular_tree_shakeable/core/ApiError.ts.snap new file mode 100644 index 000000000..36675d288 --- /dev/null +++ b/packages/openapi-ts/test/__snapshots__/test/generated/v3_angular_tree_shakeable/core/ApiError.ts.snap @@ -0,0 +1,21 @@ +import type { ApiRequestOptions } from './ApiRequestOptions'; +import type { ApiResult } from './ApiResult'; + +export class ApiError extends Error { + public readonly url: string; + public readonly status: number; + public readonly statusText: string; + public readonly body: unknown; + public readonly request: ApiRequestOptions; + + constructor(request: ApiRequestOptions, response: ApiResult, message: string) { + super(message); + + this.name = 'ApiError'; + this.url = response.url; + this.status = response.status; + this.statusText = response.statusText; + this.body = response.body; + this.request = request; + } +} \ No newline at end of file diff --git a/packages/openapi-ts/test/__snapshots__/test/generated/v3_angular_tree_shakeable/core/ApiRequestOptions.ts.snap b/packages/openapi-ts/test/__snapshots__/test/generated/v3_angular_tree_shakeable/core/ApiRequestOptions.ts.snap new file mode 100644 index 000000000..939a0aa4c --- /dev/null +++ b/packages/openapi-ts/test/__snapshots__/test/generated/v3_angular_tree_shakeable/core/ApiRequestOptions.ts.snap @@ -0,0 +1,21 @@ +export type ApiRequestOptions = { + readonly body?: any; + readonly cookies?: Record; + readonly errors?: Record; + readonly formData?: Record | any[] | Blob | File; + readonly headers?: Record; + readonly mediaType?: string; + readonly method: + | 'DELETE' + | 'GET' + | 'HEAD' + | 'OPTIONS' + | 'PATCH' + | 'POST' + | 'PUT'; + readonly path?: Record; + readonly query?: Record; + readonly responseHeader?: string; + readonly responseTransformer?: (data: unknown) => Promise; + readonly url: string; +}; \ No newline at end of file diff --git a/packages/openapi-ts/test/__snapshots__/test/generated/v3_angular_tree_shakeable/core/ApiResult.ts.snap b/packages/openapi-ts/test/__snapshots__/test/generated/v3_angular_tree_shakeable/core/ApiResult.ts.snap new file mode 100644 index 000000000..4c58e3913 --- /dev/null +++ b/packages/openapi-ts/test/__snapshots__/test/generated/v3_angular_tree_shakeable/core/ApiResult.ts.snap @@ -0,0 +1,7 @@ +export type ApiResult = { + readonly body: TData; + readonly ok: boolean; + readonly status: number; + readonly statusText: string; + readonly url: string; +}; \ No newline at end of file diff --git a/packages/openapi-ts/test/__snapshots__/test/generated/v3_angular_tree_shakeable/core/OpenAPI.ts.snap b/packages/openapi-ts/test/__snapshots__/test/generated/v3_angular_tree_shakeable/core/OpenAPI.ts.snap new file mode 100644 index 000000000..9fafe8d52 --- /dev/null +++ b/packages/openapi-ts/test/__snapshots__/test/generated/v3_angular_tree_shakeable/core/OpenAPI.ts.snap @@ -0,0 +1,55 @@ +import type { HttpResponse } from '@angular/common/http'; +import type { ApiRequestOptions } from './ApiRequestOptions'; + +type Headers = Record; +type Middleware = (value: T) => T | Promise; +type Resolver = (options: ApiRequestOptions) => Promise; + +export class Interceptors { + _fns: Middleware[]; + + constructor() { + this._fns = []; + } + + eject(fn: Middleware): void { + const index = this._fns.indexOf(fn); + if (index !== -1) { + this._fns = [...this._fns.slice(0, index), ...this._fns.slice(index + 1)]; + } + } + + use(fn: Middleware): void { + this._fns = [...this._fns, fn]; + } +} + +export type OpenAPIConfig = { + BASE: string; + CREDENTIALS: 'include' | 'omit' | 'same-origin'; + ENCODE_PATH?: ((path: string) => string) | undefined; + HEADERS?: Headers | Resolver | undefined; + PASSWORD?: string | Resolver | undefined; + TOKEN?: string | Resolver | undefined; + USERNAME?: string | Resolver | undefined; + VERSION: string; + WITH_CREDENTIALS: boolean; + interceptors: { + response: Interceptors>; + }; +}; + +export const OpenAPI: OpenAPIConfig = { + BASE: 'http://localhost:3000/base', + CREDENTIALS: 'include', + ENCODE_PATH: undefined, + HEADERS: undefined, + PASSWORD: undefined, + TOKEN: undefined, + USERNAME: undefined, + VERSION: '1.0', + WITH_CREDENTIALS: false, + interceptors: { + response: new Interceptors(), + }, +}; \ No newline at end of file diff --git a/packages/openapi-ts/test/__snapshots__/test/generated/v3_angular_tree_shakeable/core/request.ts.snap b/packages/openapi-ts/test/__snapshots__/test/generated/v3_angular_tree_shakeable/core/request.ts.snap new file mode 100644 index 000000000..4eab34ab6 --- /dev/null +++ b/packages/openapi-ts/test/__snapshots__/test/generated/v3_angular_tree_shakeable/core/request.ts.snap @@ -0,0 +1,337 @@ +import { HttpClient, HttpHeaders } from '@angular/common/http'; +import type { HttpResponse, HttpErrorResponse } from '@angular/common/http'; +import { forkJoin, of, throwError } from 'rxjs'; +import { catchError, map, switchMap } from 'rxjs/operators'; +import type { Observable } from 'rxjs'; + +import { ApiError } from './ApiError'; +import type { ApiRequestOptions } from './ApiRequestOptions'; +import type { ApiResult } from './ApiResult'; +import type { OpenAPIConfig } from './OpenAPI'; + +export const isString = (value: unknown): value is string => { + return typeof value === 'string'; +}; + +export const isStringWithValue = (value: unknown): value is string => { + return isString(value) && value !== ''; +}; + +export const isBlob = (value: any): value is Blob => { + return value instanceof Blob; +}; + +export const isFormData = (value: unknown): value is FormData => { + return value instanceof FormData; +}; + +export const base64 = (str: string): string => { + try { + return btoa(str); + } catch (err) { + // @ts-ignore + return Buffer.from(str).toString('base64'); + } +}; + +export const getQueryString = (params: Record): string => { + const qs: string[] = []; + + const append = (key: string, value: unknown) => { + qs.push(`${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`); + }; + + const encodePair = (key: string, value: unknown) => { + if (value === undefined || value === null) { + return; + } + + if (value instanceof Date) { + append(key, value.toISOString()); + } else if (Array.isArray(value)) { + value.forEach(v => encodePair(key, v)); + } else if (typeof value === 'object') { + Object.entries(value).forEach(([k, v]) => encodePair(`${key}[${k}]`, v)); + } else { + append(key, value); + } + }; + + Object.entries(params).forEach(([key, value]) => encodePair(key, value)); + + return qs.length ? `?${qs.join('&')}` : ''; +}; + +const getUrl = (config: OpenAPIConfig, options: ApiRequestOptions): string => { + const encoder = config.ENCODE_PATH || encodeURI; + + const path = options.url + .replace('{api-version}', config.VERSION) + .replace(/{(.*?)}/g, (substring: string, group: string) => { + if (options.path?.hasOwnProperty(group)) { + return encoder(String(options.path[group])); + } + return substring; + }); + + const url = config.BASE + path; + return options.query ? url + getQueryString(options.query) : url; +}; + +export const getFormData = (options: ApiRequestOptions): FormData | undefined => { + if (options.formData) { + const formData = new FormData(); + + const process = (key: string, value: unknown) => { + if (isString(value) || isBlob(value)) { + formData.append(key, value); + } else { + formData.append(key, JSON.stringify(value)); + } + }; + + Object.entries(options.formData) + .filter(([, value]) => value !== undefined && value !== null) + .forEach(([key, value]) => { + if (Array.isArray(value)) { + value.forEach(v => process(key, v)); + } else { + process(key, value); + } + }); + + return formData; + } + return undefined; +}; + +type Resolver = (options: ApiRequestOptions) => Promise; + +export const resolve = async (options: ApiRequestOptions, resolver?: T | Resolver): Promise => { + if (typeof resolver === 'function') { + return (resolver as Resolver)(options); + } + return resolver; +}; + +export const getHeaders = (config: OpenAPIConfig, options: ApiRequestOptions): Observable => { + return forkJoin({ + // @ts-ignore + token: resolve(options, config.TOKEN), + // @ts-ignore + username: resolve(options, config.USERNAME), + // @ts-ignore + password: resolve(options, config.PASSWORD), + // @ts-ignore + additionalHeaders: resolve(options, config.HEADERS), + }).pipe( + map(({ token, username, password, additionalHeaders }) => { + const headers = Object.entries({ + Accept: 'application/json', + ...additionalHeaders, + ...options.headers, + }) + .filter(([, value]) => value !== undefined && value !== null) + .reduce((headers, [key, value]) => ({ + ...headers, + [key]: String(value), + }), {} as Record); + + if (isStringWithValue(token)) { + headers['Authorization'] = `Bearer ${token}`; + } + + if (isStringWithValue(username) && isStringWithValue(password)) { + const credentials = base64(`${username}:${password}`); + headers['Authorization'] = `Basic ${credentials}`; + } + + if (options.body !== undefined) { + if (options.mediaType) { + headers['Content-Type'] = options.mediaType; + } else if (isBlob(options.body)) { + headers['Content-Type'] = options.body.type || 'application/octet-stream'; + } else if (isString(options.body)) { + headers['Content-Type'] = 'text/plain'; + } else if (!isFormData(options.body)) { + headers['Content-Type'] = 'application/json'; + } + } + + return new HttpHeaders(headers); + }), + ); +}; + +export const getRequestBody = (options: ApiRequestOptions): unknown => { + if (options.body) { + if (options.mediaType?.includes('application/json') || options.mediaType?.includes('+json')) { + return JSON.stringify(options.body); + } else if (isString(options.body) || isBlob(options.body) || isFormData(options.body)) { + return options.body; + } else { + return JSON.stringify(options.body); + } + } + return undefined; +}; + +export const sendRequest = ( + config: OpenAPIConfig, + options: ApiRequestOptions, + http: HttpClient, + url: string, + body: unknown, + formData: FormData | undefined, + headers: HttpHeaders +): Observable> => { + return http.request(options.method, url, { + headers, + body: body ?? formData, + withCredentials: config.WITH_CREDENTIALS, + observe: 'response', + }); +}; + +export const getResponseHeader = (response: HttpResponse, responseHeader?: string): string | undefined => { + if (responseHeader) { + const value = response.headers.get(responseHeader); + if (isString(value)) { + return value; + } + } + return undefined; +}; + +export const getResponseBody = (response: HttpResponse): T | undefined => { + if (response.status !== 204 && response.body !== null) { + return response.body; + } + return undefined; +}; + +export const catchErrorCodes = (options: ApiRequestOptions, result: ApiResult): void => { + const errors: Record = { + 400: 'Bad Request', + 401: 'Unauthorized', + 402: 'Payment Required', + 403: 'Forbidden', + 404: 'Not Found', + 405: 'Method Not Allowed', + 406: 'Not Acceptable', + 407: 'Proxy Authentication Required', + 408: 'Request Timeout', + 409: 'Conflict', + 410: 'Gone', + 411: 'Length Required', + 412: 'Precondition Failed', + 413: 'Payload Too Large', + 414: 'URI Too Long', + 415: 'Unsupported Media Type', + 416: 'Range Not Satisfiable', + 417: 'Expectation Failed', + 418: 'Im a teapot', + 421: 'Misdirected Request', + 422: 'Unprocessable Content', + 423: 'Locked', + 424: 'Failed Dependency', + 425: 'Too Early', + 426: 'Upgrade Required', + 428: 'Precondition Required', + 429: 'Too Many Requests', + 431: 'Request Header Fields Too Large', + 451: 'Unavailable For Legal Reasons', + 500: 'Internal Server Error', + 501: 'Not Implemented', + 502: 'Bad Gateway', + 503: 'Service Unavailable', + 504: 'Gateway Timeout', + 505: 'HTTP Version Not Supported', + 506: 'Variant Also Negotiates', + 507: 'Insufficient Storage', + 508: 'Loop Detected', + 510: 'Not Extended', + 511: 'Network Authentication Required', + ...options.errors, + } + + const error = errors[result.status]; + if (error) { + throw new ApiError(options, result, error); + } + + if (!result.ok) { + const errorStatus = result.status ?? 'unknown'; + const errorStatusText = result.statusText ?? 'unknown'; + const errorBody = (() => { + try { + return JSON.stringify(result.body, null, 2); + } catch (e) { + return undefined; + } + })(); + + throw new ApiError(options, result, + `Generic Error: status: ${errorStatus}; status text: ${errorStatusText}; body: ${errorBody}` + ); + } +}; + +/** + * Request method + * @param config The OpenAPI configuration object + * @param http The Angular HTTP client + * @param options The request options from the service + * @returns Observable + * @throws ApiError + */ +export const request = (config: OpenAPIConfig, http: HttpClient, options: ApiRequestOptions): Observable => { + const url = getUrl(config, options); + const formData = getFormData(options); + const body = getRequestBody(options); + + return getHeaders(config, options).pipe( + switchMap(headers => { + return sendRequest(config, options, http, url, body, formData, headers); + }), + switchMap(async response => { + for (const fn of config.interceptors.response._fns) { + response = await fn(response); + } + const responseBody = getResponseBody(response); + const responseHeader = getResponseHeader(response, options.responseHeader); + + let transformedBody = responseBody; + if (options.responseTransformer && response.ok) { + transformedBody = await options.responseTransformer(responseBody) + } + + return { + url, + ok: response.ok, + status: response.status, + statusText: response.statusText, + body: responseHeader ?? transformedBody, + } as ApiResult; + }), + catchError((error: HttpErrorResponse) => { + if (!error.status) { + return throwError(() => error); + } + return of({ + url, + ok: error.ok, + status: error.status, + statusText: error.statusText, + body: error.error ?? error.statusText, + } as ApiResult); + }), + map(result => { + catchErrorCodes(options, result); + return result.body as T; + }), + catchError((error: ApiError) => { + return throwError(() => error); + }), + ); +}; \ No newline at end of file diff --git a/packages/openapi-ts/test/__snapshots__/test/generated/v3_angular_tree_shakeable/index.ts.snap b/packages/openapi-ts/test/__snapshots__/test/generated/v3_angular_tree_shakeable/index.ts.snap new file mode 100644 index 000000000..d23426032 --- /dev/null +++ b/packages/openapi-ts/test/__snapshots__/test/generated/v3_angular_tree_shakeable/index.ts.snap @@ -0,0 +1,5 @@ +// This file is auto-generated by @hey-api/openapi-ts +export { ApiError } from './core/ApiError'; +export { OpenAPI, type OpenAPIConfig } from './core/OpenAPI'; +export * from './services.gen'; +export * from './types.gen'; \ No newline at end of file diff --git a/packages/openapi-ts/test/__snapshots__/test/generated/v3_angular_tree_shakeable/services.gen.ts.snap b/packages/openapi-ts/test/__snapshots__/test/generated/v3_angular_tree_shakeable/services.gen.ts.snap new file mode 100644 index 000000000..1ec81cb39 --- /dev/null +++ b/packages/openapi-ts/test/__snapshots__/test/generated/v3_angular_tree_shakeable/services.gen.ts.snap @@ -0,0 +1,668 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import type { Observable } from 'rxjs'; +import { OpenAPI } from './core/OpenAPI'; +import { request as __request } from './core/request'; +import type { ImportData, ImportResponse, ApiVversionOdataControllerCountResponse, DeleteFooData3, CallWithDescriptionsData, DeprecatedCallData, CallWithParametersData, CallWithWeirdParameterNamesData, GetCallWithOptionalParamData, PostCallWithOptionalParamData, PostCallWithOptionalParamResponse, PostApiRequestBodyData, PostApiFormDataData, CallWithDefaultParametersData, CallWithDefaultOptionalParametersData, CallToTestOrderOfParamsData, CallWithNoContentResponseResponse, CallWithResponseAndNoContentResponseResponse, DummyAResponse, DummyBResponse, CallWithResponseResponse, CallWithDuplicateResponsesResponse, CallWithResponsesResponse, CollectionFormatData, TypesData, TypesResponse, UploadFileData, UploadFileResponse, FileResponseData, FileResponseResponse, ComplexTypesData, ComplexTypesResponse, MultipartRequestData, MultipartResponseResponse, ComplexParamsData, ComplexParamsResponse, CallWithResultFromHeaderResponse, TestErrorCodeData, TestErrorCodeResponse, NonAsciiæøåÆøÅöôêÊ字符串Data, NonAsciiæøåÆøÅöôêÊ字符串Response, PutWithFormUrlEncodedData } from './types.gen'; + +/** + * @throws ApiError + */ +export const export_ = function (): Observable { return __request(OpenAPI, this.http, { + method: 'GET', + url: '/api/v{api-version}/no-tag' +}); }; + +/** + * @param data The data for the request. + * @param data.requestBody + * @returns Model_From_Zendesk Success + * @returns ModelWithReadOnlyAndWriteOnly Default success response + * @throws ApiError + */ +export const import_ = function (data: ImportData): Observable { return __request(OpenAPI, this.http, { + method: 'POST', + url: '/api/v{api-version}/no-tag', + body: data.requestBody, + mediaType: 'application/json' +}); }; + +/** + * @returns Model_From_Zendesk Success + * @throws ApiError + */ +export const apiVVersionOdataControllerCount = function (): Observable { return __request(OpenAPI, this.http, { + method: 'GET', + url: '/api/v{api-version}/simple/$count' +}); }; + +/** + * @throws ApiError + */ +export const getCallWithoutParametersAndResponse = function (): Observable { return __request(OpenAPI, this.http, { + method: 'GET', + url: '/api/v{api-version}/simple' +}); }; + +/** + * @throws ApiError + */ +export const putCallWithoutParametersAndResponse = function (): Observable { return __request(OpenAPI, this.http, { + method: 'PUT', + url: '/api/v{api-version}/simple' +}); }; + +/** + * @throws ApiError + */ +export const postCallWithoutParametersAndResponse = function (): Observable { return __request(OpenAPI, this.http, { + method: 'POST', + url: '/api/v{api-version}/simple' +}); }; + +/** + * @throws ApiError + */ +export const deleteCallWithoutParametersAndResponse = function (): Observable { return __request(OpenAPI, this.http, { + method: 'DELETE', + url: '/api/v{api-version}/simple' +}); }; + +/** + * @throws ApiError + */ +export const optionsCallWithoutParametersAndResponse = function (): Observable { return __request(OpenAPI, this.http, { + method: 'OPTIONS', + url: '/api/v{api-version}/simple' +}); }; + +/** + * @throws ApiError + */ +export const headCallWithoutParametersAndResponse = function (): Observable { return __request(OpenAPI, this.http, { + method: 'HEAD', + url: '/api/v{api-version}/simple' +}); }; + +/** + * @throws ApiError + */ +export const patchCallWithoutParametersAndResponse = function (): Observable { return __request(OpenAPI, this.http, { + method: 'PATCH', + url: '/api/v{api-version}/simple' +}); }; + +/** + * @param data The data for the request. + * @param data.fooParam foo in method + * @param data.barParam bar in method + * @param data.xFooBar Parameter with illegal characters + * @throws ApiError + */ +export const deleteFoo = function (data: DeleteFooData3): Observable { return __request(OpenAPI, this.http, { + method: 'DELETE', + url: '/api/v{api-version}/foo/{foo_param}/bar/{BarParam}', + path: { + foo_param: data.fooParam, + BarParam: data.barParam + }, + headers: { + 'x-Foo-Bar': data.xFooBar + } +}); }; + +/** + * @param data The data for the request. + * @param data.parameterWithBreaks Testing multiline comments in string: First line + * Second line + * + * Fourth line + * @param data.parameterWithBackticks Testing backticks in string: `backticks` and ```multiple backticks``` should work + * @param data.parameterWithSlashes Testing slashes in string: \backwards\\\ and /forwards/// should work + * @param data.parameterWithExpressionPlaceholders Testing expression placeholders in string: ${expression} should work + * @param data.parameterWithQuotes Testing quotes in string: 'single quote''' and "double quotes""" should work + * @param data.parameterWithReservedCharacters Testing reserved characters in string: * inline * and ** inline ** should work + * @throws ApiError + */ +export const callWithDescriptions = function (data: CallWithDescriptionsData = {}): Observable { return __request(OpenAPI, this.http, { + method: 'POST', + url: '/api/v{api-version}/descriptions/', + query: { + parameterWithBreaks: data.parameterWithBreaks, + parameterWithBackticks: data.parameterWithBackticks, + parameterWithSlashes: data.parameterWithSlashes, + parameterWithExpressionPlaceholders: data.parameterWithExpressionPlaceholders, + parameterWithQuotes: data.parameterWithQuotes, + parameterWithReservedCharacters: data.parameterWithReservedCharacters + } +}); }; + +/** + * @deprecated + * @param data The data for the request. + * @param data.parameter This parameter is deprecated + * @throws ApiError + */ +export const deprecatedCall = function (data: DeprecatedCallData): Observable { return __request(OpenAPI, this.http, { + method: 'POST', + url: '/api/v{api-version}/parameters/deprecated', + headers: { + parameter: data.parameter + } +}); }; + +/** + * @param data The data for the request. + * @param data.parameterHeader This is the parameter that goes into the header + * @param data.fooAllOfEnum + * @param data.cursor This is the parameter that goes into the query params + * @param data.parameterCookie This is the parameter that goes into the cookie + * @param data.parameterPath This is the parameter that goes into the path + * @param data.requestBody This is the parameter that goes into the body + * @param data.fooRefEnum + * @throws ApiError + */ +export const callWithParameters = function (data: CallWithParametersData): Observable { return __request(OpenAPI, this.http, { + method: 'POST', + url: '/api/v{api-version}/parameters/{parameterPath}', + path: { + parameterPath: data.parameterPath + }, + cookies: { + parameterCookie: data.parameterCookie + }, + headers: { + parameterHeader: data.parameterHeader + }, + query: { + foo_ref_enum: data.fooRefEnum, + foo_all_of_enum: data.fooAllOfEnum, + cursor: data.cursor + }, + body: data.requestBody, + mediaType: 'application/json' +}); }; + +/** + * @param data The data for the request. + * @param data.parameterHeader This is the parameter that goes into the request header + * @param data.parameterQuery This is the parameter that goes into the request query params + * @param data.parameterCookie This is the parameter that goes into the cookie + * @param data.requestBody This is the parameter that goes into the body + * @param data.parameterPath1 This is the parameter that goes into the path + * @param data.parameterPath2 This is the parameter that goes into the path + * @param data.parameterPath3 This is the parameter that goes into the path + * @param data._default This is the parameter with a reserved keyword + * @throws ApiError + */ +export const callWithWeirdParameterNames = function (data: CallWithWeirdParameterNamesData): Observable { return __request(OpenAPI, this.http, { + method: 'POST', + url: '/api/v{api-version}/parameters/{parameter.path.1}/{parameter-path-2}/{PARAMETER-PATH-3}', + path: { + 'parameter.path.1': data.parameterPath1, + 'parameter-path-2': data.parameterPath2, + 'PARAMETER-PATH-3': data.parameterPath3 + }, + cookies: { + 'PARAMETER-COOKIE': data.parameterCookie + }, + headers: { + 'parameter.header': data.parameterHeader + }, + query: { + default: data._default, + 'parameter-query': data.parameterQuery + }, + body: data.requestBody, + mediaType: 'application/json' +}); }; + +/** + * @param data The data for the request. + * @param data.requestBody This is a required parameter + * @param data.page This is an optional parameter + * @throws ApiError + */ +export const getCallWithOptionalParam = function (data: GetCallWithOptionalParamData): Observable { return __request(OpenAPI, this.http, { + method: 'GET', + url: '/api/v{api-version}/parameters/', + query: { + page: data.page + }, + body: data.requestBody, + mediaType: 'application/json' +}); }; + +/** + * @param data The data for the request. + * @param data.parameter This is a required parameter + * @param data.requestBody This is an optional parameter + * @returns number Response is a simple number + * @returns void Success + * @throws ApiError + */ +export const postCallWithOptionalParam = function (data: PostCallWithOptionalParamData): Observable { return __request(OpenAPI, this.http, { + method: 'POST', + url: '/api/v{api-version}/parameters/', + query: { + parameter: data.parameter + }, + body: data.requestBody, + mediaType: 'application/json' +}); }; + +/** + * @param data The data for the request. + * @param data.parameter This is a reusable parameter + * @param data.foo A reusable request body + * @throws ApiError + */ +export const postApiRequestBody = function (data: PostApiRequestBodyData = {}): Observable { return __request(OpenAPI, this.http, { + method: 'POST', + url: '/api/v{api-version}/requestBody/', + query: { + parameter: data.parameter + }, + body: data.foo, + mediaType: 'application/json' +}); }; + +/** + * @param data The data for the request. + * @param data.parameter This is a reusable parameter + * @param data.formData A reusable request body + * @throws ApiError + */ +export const postApiFormData = function (data: PostApiFormDataData = {}): Observable { return __request(OpenAPI, this.http, { + method: 'POST', + url: '/api/v{api-version}/formData/', + query: { + parameter: data.parameter + }, + formData: data.formData, + mediaType: 'multipart/form-data' +}); }; + +/** + * @param data The data for the request. + * @param data.parameterString This is a simple string with default value + * @param data.parameterNumber This is a simple number with default value + * @param data.parameterBoolean This is a simple boolean with default value + * @param data.parameterEnum This is a simple enum with default value + * @param data.parameterModel This is a simple model with default value + * @throws ApiError + */ +export const callWithDefaultParameters = function (data: CallWithDefaultParametersData = {}): Observable { return __request(OpenAPI, this.http, { + method: 'GET', + url: '/api/v{api-version}/defaults', + query: { + parameterString: data.parameterString, + parameterNumber: data.parameterNumber, + parameterBoolean: data.parameterBoolean, + parameterEnum: data.parameterEnum, + parameterModel: data.parameterModel + } +}); }; + +/** + * @param data The data for the request. + * @param data.parameterString This is a simple string that is optional with default value + * @param data.parameterNumber This is a simple number that is optional with default value + * @param data.parameterBoolean This is a simple boolean that is optional with default value + * @param data.parameterEnum This is a simple enum that is optional with default value + * @param data.parameterModel This is a simple model that is optional with default value + * @throws ApiError + */ +export const callWithDefaultOptionalParameters = function (data: CallWithDefaultOptionalParametersData = {}): Observable { return __request(OpenAPI, this.http, { + method: 'POST', + url: '/api/v{api-version}/defaults', + query: { + parameterString: data.parameterString, + parameterNumber: data.parameterNumber, + parameterBoolean: data.parameterBoolean, + parameterEnum: data.parameterEnum, + parameterModel: data.parameterModel + } +}); }; + +/** + * @param data The data for the request. + * @param data.parameterStringWithNoDefault This is a string with no default + * @param data.parameterOptionalStringWithDefault This is a optional string with default + * @param data.parameterOptionalStringWithEmptyDefault This is a optional string with empty default + * @param data.parameterOptionalStringWithNoDefault This is a optional string with no default + * @param data.parameterStringWithDefault This is a string with default + * @param data.parameterStringWithEmptyDefault This is a string with empty default + * @param data.parameterStringNullableWithNoDefault This is a string that can be null with no default + * @param data.parameterStringNullableWithDefault This is a string that can be null with default + * @throws ApiError + */ +export const callToTestOrderOfParams = function (data: CallToTestOrderOfParamsData): Observable { return __request(OpenAPI, this.http, { + method: 'PUT', + url: '/api/v{api-version}/defaults', + query: { + parameterOptionalStringWithDefault: data.parameterOptionalStringWithDefault, + parameterOptionalStringWithEmptyDefault: data.parameterOptionalStringWithEmptyDefault, + parameterOptionalStringWithNoDefault: data.parameterOptionalStringWithNoDefault, + parameterStringWithDefault: data.parameterStringWithDefault, + parameterStringWithEmptyDefault: data.parameterStringWithEmptyDefault, + parameterStringWithNoDefault: data.parameterStringWithNoDefault, + parameterStringNullableWithNoDefault: data.parameterStringNullableWithNoDefault, + parameterStringNullableWithDefault: data.parameterStringNullableWithDefault + } +}); }; + +/** + * @throws ApiError + */ +export const duplicateName = function (): Observable { return __request(OpenAPI, this.http, { + method: 'GET', + url: '/api/v{api-version}/duplicate' +}); }; + +/** + * @throws ApiError + */ +export const duplicateName1 = function (): Observable { return __request(OpenAPI, this.http, { + method: 'POST', + url: '/api/v{api-version}/duplicate' +}); }; + +/** + * @throws ApiError + */ +export const duplicateName2 = function (): Observable { return __request(OpenAPI, this.http, { + method: 'PUT', + url: '/api/v{api-version}/duplicate' +}); }; + +/** + * @throws ApiError + */ +export const duplicateName3 = function (): Observable { return __request(OpenAPI, this.http, { + method: 'DELETE', + url: '/api/v{api-version}/duplicate' +}); }; + +/** + * @returns void Success + * @throws ApiError + */ +export const callWithNoContentResponse = function (): Observable { return __request(OpenAPI, this.http, { + method: 'GET', + url: '/api/v{api-version}/no-content' +}); }; + +/** + * @returns number Response is a simple number + * @returns void Success + * @throws ApiError + */ +export const callWithResponseAndNoContentResponse = function (): Observable { return __request(OpenAPI, this.http, { + method: 'GET', + url: '/api/v{api-version}/multiple-tags/response-and-no-content' +}); }; + +/** + * @returns _400 + * @throws ApiError + */ +export const dummyA = function (): Observable { return __request(OpenAPI, this.http, { + method: 'GET', + url: '/api/v{api-version}/multiple-tags/a' +}); }; + +/** + * @returns void Success + * @throws ApiError + */ +export const dummyB = function (): Observable { return __request(OpenAPI, this.http, { + method: 'GET', + url: '/api/v{api-version}/multiple-tags/b' +}); }; + +/** + * @returns import + * @throws ApiError + */ +export const callWithResponse = function (): Observable { return __request(OpenAPI, this.http, { + method: 'GET', + url: '/api/v{api-version}/response' +}); }; + +/** + * @returns unknown Message for 200 response + * @returns ModelWithString Message for 201 response + * @returns ModelWithString Message for 202 response + * @throws ApiError + */ +export const callWithDuplicateResponses = function (): Observable { return __request(OpenAPI, this.http, { + method: 'POST', + url: '/api/v{api-version}/response', + errors: { + 500: 'Message for 500 error', + 501: 'Message for 501 error', + 502: 'Message for 502 error', + '4XX': 'Message for 4XX errors', + default: 'Default error response' + } +}); }; + +/** + * @returns unknown Message for 200 response + * @returns ModelThatExtends Message for 201 response + * @returns ModelThatExtendsExtends Message for 202 response + * @throws ApiError + */ +export const callWithResponses = function (): Observable { return __request(OpenAPI, this.http, { + method: 'PUT', + url: '/api/v{api-version}/response', + errors: { + 500: 'Message for 500 error', + 501: 'Message for 501 error', + 502: 'Message for 502 error', + default: 'Message for default response' + } +}); }; + +/** + * @param data The data for the request. + * @param data.parameterArrayCsv This is an array parameter that is sent as csv format (comma-separated values) + * @param data.parameterArraySsv This is an array parameter that is sent as ssv format (space-separated values) + * @param data.parameterArrayTsv This is an array parameter that is sent as tsv format (tab-separated values) + * @param data.parameterArrayPipes This is an array parameter that is sent as pipes format (pipe-separated values) + * @param data.parameterArrayMulti This is an array parameter that is sent as multi format (multiple parameter instances) + * @throws ApiError + */ +export const collectionFormat = function (data: CollectionFormatData): Observable { return __request(OpenAPI, this.http, { + method: 'GET', + url: '/api/v{api-version}/collectionFormat', + query: { + parameterArrayCSV: data.parameterArrayCsv, + parameterArraySSV: data.parameterArraySsv, + parameterArrayTSV: data.parameterArrayTsv, + parameterArrayPipes: data.parameterArrayPipes, + parameterArrayMulti: data.parameterArrayMulti + } +}); }; + +/** + * @param data The data for the request. + * @param data.parameterArray This is an array parameter + * @param data.parameterDictionary This is a dictionary parameter + * @param data.parameterEnum This is an enum parameter + * @param data.parameterNumber This is a number parameter + * @param data.parameterString This is a string parameter + * @param data.parameterBoolean This is a boolean parameter + * @param data.parameterObject This is an object parameter + * @param data.id This is a number parameter + * @returns number Response is a simple number + * @returns string Response is a simple string + * @returns boolean Response is a simple boolean + * @returns unknown Response is a simple object + * @throws ApiError + */ +export const types = function (data: TypesData): Observable { return __request(OpenAPI, this.http, { + method: 'GET', + url: '/api/v{api-version}/types', + path: { + id: data.id + }, + query: { + parameterNumber: data.parameterNumber, + parameterString: data.parameterString, + parameterBoolean: data.parameterBoolean, + parameterObject: data.parameterObject, + parameterArray: data.parameterArray, + parameterDictionary: data.parameterDictionary, + parameterEnum: data.parameterEnum + } +}); }; + +/** + * @param data The data for the request. + * @param data.formData + * @returns boolean + * @throws ApiError + */ +export const uploadFile = function (data: UploadFileData): Observable { return __request(OpenAPI, this.http, { + method: 'POST', + url: '/api/v{api-version}/upload', + formData: data.formData, + mediaType: 'application/x-www-form-urlencoded' +}); }; + +/** + * @param data The data for the request. + * @param data.id + * @returns binary Success + * @throws ApiError + */ +export const fileResponse = function (data: FileResponseData): Observable { return __request(OpenAPI, this.http, { + method: 'GET', + url: '/api/v{api-version}/file/{id}', + path: { + id: data.id + } +}); }; + +/** + * @param data The data for the request. + * @param data.parameterObject Parameter containing object + * @param data.parameterReference Parameter containing reference + * @returns ModelWithString Successful response + * @throws ApiError + */ +export const complexTypes = function (data: ComplexTypesData): Observable { return __request(OpenAPI, this.http, { + method: 'GET', + url: '/api/v{api-version}/complex', + query: { + parameterObject: data.parameterObject, + parameterReference: data.parameterReference + }, + errors: { + 400: '400 `server` error', + 500: '500 server error' + } +}); }; + +/** + * @param data The data for the request. + * @param data.formData + * @throws ApiError + */ +export const multipartRequest = function (data: MultipartRequestData = {}): Observable { return __request(OpenAPI, this.http, { + method: 'POST', + url: '/api/v{api-version}/multipart', + formData: data.formData, + mediaType: 'multipart/form-data' +}); }; + +/** + * @returns unknown OK + * @throws ApiError + */ +export const multipartResponse = function (): Observable { return __request(OpenAPI, this.http, { + method: 'GET', + url: '/api/v{api-version}/multipart' +}); }; + +/** + * @param data The data for the request. + * @param data.id + * @param data.requestBody + * @returns ModelWithString Success + * @throws ApiError + */ +export const complexParams = function (data: ComplexParamsData): Observable { return __request(OpenAPI, this.http, { + method: 'PUT', + url: '/api/v{api-version}/complex/{id}', + path: { + id: data.id + }, + body: data.requestBody, + mediaType: 'application/json-patch+json' +}); }; + +/** + * @returns string Successful response + * @throws ApiError + */ +export const callWithResultFromHeader = function (): Observable { return __request(OpenAPI, this.http, { + method: 'POST', + url: '/api/v{api-version}/header', + responseHeader: 'operation-location', + errors: { + 400: '400 server error', + 500: '500 server error' + } +}); }; + +/** + * @param data The data for the request. + * @param data.status Status code to return + * @returns unknown Custom message: Successful response + * @throws ApiError + */ +export const testErrorCode = function (data: TestErrorCodeData): Observable { return __request(OpenAPI, this.http, { + method: 'POST', + url: '/api/v{api-version}/error', + query: { + status: data.status + }, + errors: { + 500: 'Custom message: Internal Server Error', + 501: 'Custom message: Not Implemented', + 502: 'Custom message: Bad Gateway', + 503: 'Custom message: Service Unavailable' + } +}); }; + +/** + * @param data The data for the request. + * @param data.nonAsciiParamæøåÆøÅöôêÊ Dummy input param + * @returns NonAsciiStringæøåÆØÅöôêÊ字符串 Successful response + * @throws ApiError + */ +export const nonAsciiæøåÆøÅöôêÊ字符串 = function (data: NonAsciiæøåÆøÅöôêÊ字符串Data): Observable { return __request(OpenAPI, this.http, { + method: 'POST', + url: '/api/v{api-version}/non-ascii-æøåÆØÅöôêÊ字符串', + query: { + 'nonAsciiParamæøåÆØÅöôêÊ': data.nonAsciiParamæøåÆøÅöôêÊ + } +}); }; + +/** + * Login User + * @param data The data for the request. + * @param data.formData + * @throws ApiError + */ +export const putWithFormUrlEncoded = function (data: PutWithFormUrlEncodedData): Observable { return __request(OpenAPI, this.http, { + method: 'PUT', + url: '/api/v{api-version}/non-ascii-æøåÆØÅöôêÊ字符串', + formData: data.formData, + mediaType: 'application/x-www-form-urlencoded' +}); }; \ No newline at end of file diff --git a/packages/openapi-ts/test/__snapshots__/test/generated/v3_angular_tree_shakeable/types.gen.ts.snap b/packages/openapi-ts/test/__snapshots__/test/generated/v3_angular_tree_shakeable/types.gen.ts.snap new file mode 100644 index 000000000..d155e1045 --- /dev/null +++ b/packages/openapi-ts/test/__snapshots__/test/generated/v3_angular_tree_shakeable/types.gen.ts.snap @@ -0,0 +1,1389 @@ +// This file is auto-generated by @hey-api/openapi-ts + +/** + * Model with number-only name + */ +export type _400 = string; + +/** + * Testing multiline comments in string: First line + * Second line + * + * Fourth line + */ +export type camelCaseCommentWithBreaks = number; + +/** + * Testing multiline comments in string: First line + * Second line + * + * Fourth line + */ +export type CommentWithBreaks = number; + +/** + * Testing backticks in string: `backticks` and ```multiple backticks``` should work + */ +export type CommentWithBackticks = number; + +/** + * Testing backticks and quotes in string: `backticks`, 'quotes', "double quotes" and ```multiple backticks``` should work + */ +export type CommentWithBackticksAndQuotes = number; + +/** + * Testing slashes in string: \backwards\\\ and /forwards/// should work + */ +export type CommentWithSlashes = number; + +/** + * Testing expression placeholders in string: ${expression} should work + */ +export type CommentWithExpressionPlaceholders = number; + +/** + * Testing quotes in string: 'single quote''' and "double quotes""" should work + */ +export type CommentWithQuotes = number; + +/** + * Testing reserved characters in string: * inline * and ** inline ** should work + */ +export type CommentWithReservedCharacters = number; + +/** + * This is a simple number + */ +export type SimpleInteger = number; + +/** + * This is a simple boolean + */ +export type SimpleBoolean = boolean; + +/** + * This is a simple string + */ +export type SimpleString = string; + +/** + * A string with non-ascii (unicode) characters valid in typescript identifiers (æøåÆØÅöÔèÈ字符串) + */ +export type NonAsciiStringæøåÆØÅöôêÊ字符串 = string; + +/** + * This is a simple file + */ +export type SimpleFile = (Blob | File); + +/** + * This is a simple reference + */ +export type SimpleReference = ModelWithString; + +/** + * This is a simple string + */ +export type SimpleStringWithPattern = (string) | null; + +/** + * This is a simple enum with strings + */ +export type EnumWithStrings = 'Success' | 'Warning' | 'Error' | "'Single Quote'" | '"Double Quotes"' | 'Non-ascii: øæåôöØÆÅÔÖ字符串'; + +export type EnumWithReplacedCharacters = "'Single Quote'" | '"Double Quotes"' | 'øæåôöØÆÅÔÖ字符串' | 3.1 | ''; + +/** + * This is a simple enum with numbers + */ +export type EnumWithNumbers = 1 | 2 | 3 | 1.1 | 1.2 | 1.3 | 100 | 200 | 300 | -100 | -200 | -300 | -1.1 | -1.2 | -1.3; + +/** + * Success=1,Warning=2,Error=3 + */ +export type EnumFromDescription = number; + +/** + * This is a simple enum with numbers + */ +export type EnumWithExtensions = 200 | 400 | 500; + +export type EnumWithXEnumNames = 0 | 1 | 2; + +/** + * This is a simple array with numbers + */ +export type ArrayWithNumbers = Array<(number)>; + +/** + * This is a simple array with booleans + */ +export type ArrayWithBooleans = Array<(boolean)>; + +/** + * This is a simple array with strings + */ +export type ArrayWithStrings = Array<(string)>; + +/** + * This is a simple array with references + */ +export type ArrayWithReferences = Array; + +/** + * This is a simple array containing an array + */ +export type ArrayWithArray = Array>; + +/** + * This is a simple array with properties + */ +export type ArrayWithProperties = Array<{ + '16x16'?: camelCaseCommentWithBreaks; + bar?: string; +}>; + +/** + * This is a simple array with any of properties + */ +export type ArrayWithAnyOfProperties = Array<({ + foo?: string; +} | { + bar?: string; +})>; + +export type AnyOfAnyAndNull = { + data?: (unknown | null); +}; + +/** + * This is a simple array with any of properties + */ +export type AnyOfArrays = { + results?: Array<({ + foo?: string; +} | { + bar?: string; +})>; +}; + +/** + * This is a string dictionary + */ +export type DictionaryWithString = { + [key: string]: (string); +}; + +export type DictionaryWithPropertiesAndAdditionalProperties = { + foo?: number; + bar?: boolean; + [key: string]: (string | number | boolean) | undefined; +}; + +/** + * This is a string reference + */ +export type DictionaryWithReference = { + [key: string]: ModelWithString; +}; + +/** + * This is a complex dictionary + */ +export type DictionaryWithArray = { + [key: string]: Array; +}; + +/** + * This is a string dictionary + */ +export type DictionaryWithDictionary = { + [key: string]: { + [key: string]: (string); + }; +}; + +/** + * This is a complex dictionary + */ +export type DictionaryWithProperties = { + [key: string]: { + foo?: string; + bar?: string; + }; +}; + +/** + * This is a model with one number property + */ +export type ModelWithInteger = { + /** + * This is a simple number property + */ + prop?: number; +}; + +/** + * This is a model with one boolean property + */ +export type ModelWithBoolean = { + /** + * This is a simple boolean property + */ + prop?: boolean; +}; + +/** + * This is a model with one string property + */ +export type ModelWithString = { + /** + * This is a simple string property + */ + prop?: string; +}; + +/** + * This is a model with one string property + */ +export type ModelWithStringError = { + /** + * This is a simple string property + */ + prop?: string; +}; + +/** + * `Comment` or `VoiceComment`. The JSON object for adding voice comments to tickets is different. See [Adding voice comments to tickets](/documentation/ticketing/managing-tickets/adding-voice-comments-to-tickets) + */ +export type Model_From_Zendesk = string; + +/** + * This is a model with one string property + */ +export type ModelWithNullableString = { + /** + * This is a simple string property + */ + nullableProp1?: (string) | null; + /** + * This is a simple string property + */ + nullableRequiredProp1: (string) | null; + /** + * This is a simple string property + */ + nullableProp2?: (string) | null; + /** + * This is a simple string property + */ + nullableRequiredProp2: (string) | null; + /** + * This is a simple enum with strings + */ + 'foo_bar-enum'?: 'Success' | 'Warning' | 'Error' | 'ØÆÅ字符串'; +}; + +/** + * This is a simple enum with strings + */ +export type foo_bar_enum = 'Success' | 'Warning' | 'Error' | 'ØÆÅ字符串'; + +/** + * This is a model with one enum + */ +export type ModelWithEnum = { + /** + * This is a simple enum with strings + */ + 'foo_bar-enum'?: 'Success' | 'Warning' | 'Error' | 'ØÆÅ字符串'; + /** + * These are the HTTP error code enums + */ + statusCode?: '100' | '200 FOO' | '300 FOO_BAR' | '400 foo-bar' | '500 foo.bar' | '600 foo&bar'; + /** + * Simple boolean enum + */ + bool?: boolean; +}; + +/** + * These are the HTTP error code enums + */ +export type statusCode = '100' | '200 FOO' | '300 FOO_BAR' | '400 foo-bar' | '500 foo.bar' | '600 foo&bar'; + +/** + * This is a model with one enum with escaped name + */ +export type ModelWithEnumWithHyphen = { + 'foo-bar-baz-qux'?: '3.0'; +}; + +export type foo_bar_baz_qux = '3.0'; + +/** + * This is a model with one enum + */ +export type ModelWithEnumFromDescription = { + /** + * Success=1,Warning=2,Error=3 + */ + test?: number; +}; + +/** + * This is a model with nested enums + */ +export type ModelWithNestedEnums = { + dictionaryWithEnum?: { + [key: string]: ('Success' | 'Warning' | 'Error'); + }; + dictionaryWithEnumFromDescription?: { + [key: string]: (number); + }; + arrayWithEnum?: Array<('Success' | 'Warning' | 'Error')>; + arrayWithDescription?: Array<(number)>; + /** + * This is a simple enum with strings + */ + 'foo_bar-enum'?: 'Success' | 'Warning' | 'Error' | 'ØÆÅ字符串'; +}; + +/** + * This is a model with one property containing a reference + */ +export type ModelWithReference = { + prop?: ModelWithProperties; +}; + +/** + * This is a model with one property containing an array + */ +export type ModelWithArrayReadOnlyAndWriteOnly = { + prop?: Array; + propWithFile?: Array<((Blob | File))>; + propWithNumber?: Array<(number)>; +}; + +/** + * This is a model with one property containing an array + */ +export type ModelWithArray = { + prop?: Array; + propWithFile?: Array<((Blob | File))>; + propWithNumber?: Array<(number)>; +}; + +/** + * This is a model with one property containing a dictionary + */ +export type ModelWithDictionary = { + prop?: { + [key: string]: (string); + }; +}; + +/** + * This is a deprecated model with a deprecated property + * @deprecated + */ +export type DeprecatedModel = { + /** + * This is a deprecated property + * @deprecated + */ + prop?: string; +}; + +/** + * This is a model with one property containing a circular reference + */ +export type ModelWithCircularReference = { + prop?: ModelWithCircularReference; +}; + +/** + * This is a model with one property with a 'one of' relationship + */ +export type CompositionWithOneOf = { + propA?: (ModelWithString | ModelWithEnum | ModelWithArray | ModelWithDictionary); +}; + +/** + * This is a model with one property with a 'one of' relationship where the options are not $ref + */ +export type CompositionWithOneOfAnonymous = { + propA?: ({ + propA?: string; +} | string | number); +}; + +/** + * Circle + */ +export type ModelCircle = { + kind: 'circle'; + radius?: number; +}; + +/** + * Square + */ +export type ModelSquare = { + kind: 'square'; + sideLength?: number; +}; + +/** + * This is a model with one property with a 'one of' relationship where the options are not $ref + */ +export type CompositionWithOneOfDiscriminator = ModelCircle | ModelSquare; + +/** + * This is a model with one property with a 'any of' relationship + */ +export type CompositionWithAnyOf = { + propA?: (ModelWithString | ModelWithEnum | ModelWithArray | ModelWithDictionary); +}; + +/** + * This is a model with one property with a 'any of' relationship where the options are not $ref + */ +export type CompositionWithAnyOfAnonymous = { + propA?: ({ + propA?: string; +} | string | number); +}; + +/** + * This is a model with nested 'any of' property with a type null + */ +export type CompositionWithNestedAnyAndTypeNull = { + propA?: (Array<(ModelWithDictionary | null)> | Array<(ModelWithArray | null)>); +}; + +export type _3e_num_1Период = 'Bird' | 'Dog'; + +export type ConstValue = "ConstValue"; + +/** + * This is a model with one property with a 'any of' relationship where the options are not $ref + */ +export type CompositionWithNestedAnyOfAndNull = { + propA?: (Array<(_3e_num_1Период | ConstValue)> | null); +}; + +/** + * This is a model with one property with a 'one of' relationship + */ +export type CompositionWithOneOfAndNullable = { + propA?: (({ + boolean?: boolean; +} | ModelWithEnum | ModelWithArray | ModelWithDictionary) | null); +}; + +/** + * This is a model that contains a simple dictionary within composition + */ +export type CompositionWithOneOfAndSimpleDictionary = { + propA?: (boolean | { + [key: string]: (number); +}); +}; + +/** + * This is a model that contains a dictionary of simple arrays within composition + */ +export type CompositionWithOneOfAndSimpleArrayDictionary = { + propA?: (boolean | { + [key: string]: Array<(boolean)>; +}); +}; + +/** + * This is a model that contains a dictionary of complex arrays (composited) within composition + */ +export type CompositionWithOneOfAndComplexArrayDictionary = { + propA?: (boolean | { + [key: string]: Array<(number | string)>; +}); +}; + +/** + * This is a model with one property with a 'all of' relationship + */ +export type CompositionWithAllOfAndNullable = { + propA?: (({ + boolean?: boolean; +} & ModelWithEnum & ModelWithArray & ModelWithDictionary) | null); +}; + +/** + * This is a model with one property with a 'any of' relationship + */ +export type CompositionWithAnyOfAndNullable = { + propA?: (({ + boolean?: boolean; +} | ModelWithEnum | ModelWithArray | ModelWithDictionary) | null); +}; + +/** + * This is a base model with two simple optional properties + */ +export type CompositionBaseModel = { + firstName?: string; + lastname?: string; +}; + +/** + * This is a model that extends the base model + */ +export type CompositionExtendedModel = CompositionBaseModel & { + firstName: string; + lastname: string; + age: number; +}; + +/** + * This is a model with one nested property + */ +export type ModelWithProperties = { + required: string; + readonly requiredAndReadOnly: string; + requiredAndNullable: (string) | null; + string?: string; + number?: number; + boolean?: boolean; + reference?: ModelWithString; + 'property with space'?: string; + default?: string; + try?: string; + readonly '@namespace.string'?: string; + readonly '@namespace.integer'?: number; +}; + +/** + * This is a model with one nested property + */ +export type ModelWithNestedProperties = { + readonly first: { + readonly second: { + readonly third: (string) | null; + } | null; + } | null; +}; + +/** + * This is a model with duplicated properties + */ +export type ModelWithDuplicateProperties = { + prop?: ModelWithString; +}; + +/** + * This is a model with ordered properties + */ +export type ModelWithOrderedProperties = { + zebra?: string; + apple?: string; + hawaii?: string; +}; + +/** + * This is a model with duplicated imports + */ +export type ModelWithDuplicateImports = { + propA?: ModelWithString; + propB?: ModelWithString; + propC?: ModelWithString; +}; + +/** + * This is a model that extends another model + */ +export type ModelThatExtends = ModelWithString & { + propExtendsA?: string; + propExtendsB?: ModelWithString; +}; + +/** + * This is a model that extends another model + */ +export type ModelThatExtendsExtends = ModelWithString & ModelThatExtends & { + propExtendsC?: string; + propExtendsD?: ModelWithString; +}; + +/** + * This is a model that contains a some patterns + */ +export type ModelWithPattern = { + key: string; + name: string; + readonly enabled?: boolean; + readonly modified?: string; + id?: string; + text?: string; + patternWithSingleQuotes?: string; + patternWithNewline?: string; + patternWithBacktick?: string; +}; + +export type File = { + readonly id?: string; + readonly updated_at?: string; + readonly created_at?: string; + mime: string; + readonly file?: string; +}; + +export type _default = { + name?: string; +}; + +export type Pageable = { + page?: number; + size?: number; + sort?: Array<(string)>; +}; + +/** + * This is a free-form object without additionalProperties. + */ +export type FreeFormObjectWithoutAdditionalProperties = { + [key: string]: unknown; +}; + +/** + * This is a free-form object with additionalProperties: true. + */ +export type FreeFormObjectWithAdditionalPropertiesEqTrue = { + [key: string]: unknown; +}; + +/** + * This is a free-form object with additionalProperties: {}. + */ +export type FreeFormObjectWithAdditionalPropertiesEqEmptyObject = { + [key: string]: unknown; +}; + +export type ModelWithConst = { + String?: "String"; + number?: 0; + null?: null; + withType?: "Some string"; +}; + +/** + * This is a model with one property and additionalProperties: true + */ +export type ModelWithAdditionalPropertiesEqTrue = { + /** + * This is a simple string property + */ + prop?: string; + [key: string]: unknown | string; +}; + +export type NestedAnyOfArraysNullable = { + nullableArray?: (Array<(string | boolean)> | null); +}; + +export type CompositionWithOneOfAndProperties = ({ + foo: ParameterSimpleParameter; +} | { + bar: NonAsciiStringæøåÆØÅöôêÊ字符串; +}) & { + baz: (number) | null; + qux: number; +}; + +/** + * An object that can be null + */ +export type NullableObject = { + foo?: string; +} | null; + +/** + * Some % character + */ +export type CharactersInDescription = string; + +export type ModelWithNullableObject = { + data?: NullableObject; +}; + +export type ModelWithOneOfEnum = { + foo: 'Bar'; +} | { + foo: 'Baz'; +} | { + foo: 'Qux'; +} | { + content: string; + foo: 'Quux'; +} | { + content: [ + (string), + (string) + ]; + foo: 'Corge'; +}; + +export type foo = 'Bar'; + +export type ModelWithNestedArrayEnumsDataFoo = 'foo' | 'bar'; + +export type ModelWithNestedArrayEnumsDataBar = 'baz' | 'qux'; + +export type ModelWithNestedArrayEnumsData = { + foo?: Array; + bar?: Array; +}; + +export type ModelWithNestedArrayEnums = { + array_strings?: Array<(string)>; + data?: (ModelWithNestedArrayEnumsData); +}; + +export type ModelWithNestedCompositionEnums = { + foo?: (ModelWithNestedArrayEnumsDataFoo); +}; + +export type ModelWithReadOnlyAndWriteOnly = { + foo: string; + readonly bar: string; + baz: string; +}; + +export type ModelWithConstantSizeArray = [ + number, + number +]; + +export type ModelWithAnyOfConstantSizeArray = [ + (number | string), + (number | string), + (number | string) +]; + +export type ModelWithPrefixItemsConstantSizeArray = [ + ModelWithInteger, + (number | string), + string +]; + +export type ModelWithAnyOfConstantSizeArrayNullable = [ + ((number) | null | string), + ((number) | null | string), + ((number) | null | string) +]; + +export type ModelWithAnyOfConstantSizeArrayWithNSizeAndOptions = [ + (number | _import), + (number | _import) +]; + +export type ModelWithAnyOfConstantSizeArrayAndIntersect = [ + (number & string), + (number & string) +]; + +export type ModelWithNumericEnumUnion = { + /** + * Период + */ + value?: -10 | -1 | 0 | 1 | 3 | 6 | 12; +}; + +/** + * Период + */ +export type value = -10 | -1 | 0 | 1 | 3 | 6 | 12; + +/** + * Some description with `back ticks` + */ +export type ModelWithBackticksInDescription = { + /** + * The template `that` should be used for parsing and importing the contents of the CSV file. + * + *

There is one placeholder currently supported:

  • ${x} - refers to the n-th column in the CSV file, e.g. ${1}, ${2}, ...)

Example of a correct JSON template:

+ *
+     * [
+     * {
+     * "resourceType": "Asset",
+     * "identifier": {
+     * "name": "${1}",
+     * "domain": {
+     * "name": "${2}",
+     * "community": {
+     * "name": "Some Community"
+     * }
+     * }
+     * },
+     * "attributes" : {
+     * "00000000-0000-0000-0000-000000003115" : [ {
+     * "value" : "${3}"
+     * } ],
+     * "00000000-0000-0000-0000-000000000222" : [ {
+     * "value" : "${4}"
+     * } ]
+     * }
+     * }
+     * ]
+     * 
+ */ + template?: string; +}; + +export type ModelWithOneOfAndProperties = (ParameterSimpleParameter | NonAsciiStringæøåÆØÅöôêÊ字符串) & { + baz: (number) | null; + qux: number; +}; + +/** + * Model used to test deduplication strategy (unused) + */ +export type ParameterSimpleParameterUnused = string; + +/** + * Model used to test deduplication strategy + */ +export type PostServiceWithEmptyTagResponse = string; + +/** + * Model used to test deduplication strategy + */ +export type PostServiceWithEmptyTagResponse2 = string; + +/** + * Model used to test deduplication strategy + */ +export type DeleteFooData = string; + +/** + * Model used to test deduplication strategy + */ +export type DeleteFooData2 = string; + +/** + * Model with restricted keyword name + */ +export type _import = string; + +export type SchemaWithFormRestrictedKeys = { + description?: string; + 'x-enum-descriptions'?: string; + 'x-enum-varnames'?: string; + 'x-enumNames'?: string; + title?: string; + object?: { + description?: string; + 'x-enum-descriptions'?: string; + 'x-enum-varnames'?: string; + 'x-enumNames'?: string; + title?: string; + }; + array?: Array<({ + description?: string; + 'x-enum-descriptions'?: string; + 'x-enum-varnames'?: string; + 'x-enumNames'?: string; + title?: string; +})>; +}; + +/** + * This schema was giving PascalCase transformations a hard time + */ +export type io_k8s_apimachinery_pkg_apis_meta_v1_DeleteOptions = { + /** + * Must be fulfilled before a deletion is carried out. If not possible, a 409 Conflict status will be returned. + */ + preconditions?: (io_k8s_apimachinery_pkg_apis_meta_v1_Preconditions); +}; + +/** + * This schema was giving PascalCase transformations a hard time + */ +export type io_k8s_apimachinery_pkg_apis_meta_v1_Preconditions = { + /** + * Specifies the target ResourceVersion + */ + resourceVersion?: string; + /** + * Specifies the target UID. + */ + uid?: string; +}; + +export type AdditionalPropertiesUnknownIssue = { + [key: string]: (string | number); +}; + +export type AdditionalPropertiesUnknownIssue2 = { + [key: string]: (string | number); +}; + +export type AdditionalPropertiesUnknownIssue3 = string & { + entries: { + [key: string]: AdditionalPropertiesUnknownIssue; + }; +}; + +export type AdditionalPropertiesIntegerIssue = { + value: number; + [key: string]: (number) | undefined; +}; + +export type OneOfAllOfIssue = ((ConstValue | Generic_Schema_Duplicate_Issue_1_System_Boolean_) & _3e_num_1Период) | Generic_Schema_Duplicate_Issue_1_System_String_; + +export type Generic_Schema_Duplicate_Issue_1_System_Boolean_ = { + item?: boolean; + error?: (string) | null; + readonly hasError?: boolean; +}; + +export type Generic_Schema_Duplicate_Issue_1_System_String_ = { + item?: (string) | null; + error?: (string) | null; + readonly hasError?: boolean; +}; + +/** + * This is a reusable parameter + */ +export type ParameterSimpleParameter = string; + +/** + * Parameter with illegal characters + */ +export type Parameterx_Foo_Bar = ModelWithString; + +export type ImportData = { + requestBody: (ModelWithReadOnlyAndWriteOnly | ModelWithArrayReadOnlyAndWriteOnly); +}; + +export type ImportResponse = (Model_From_Zendesk | ModelWithReadOnlyAndWriteOnly); + +export type ApiVversionOdataControllerCountResponse = (Model_From_Zendesk); + +export type DeleteFooData3 = { + /** + * bar in method + */ + barParam: string; + /** + * foo in method + */ + fooParam: string; + /** + * Parameter with illegal characters + */ + xFooBar: ModelWithString; +}; + +export type CallWithDescriptionsData = { + /** + * Testing backticks in string: `backticks` and ```multiple backticks``` should work + */ + parameterWithBackticks?: unknown; + /** + * Testing multiline comments in string: First line + * Second line + * + * Fourth line + */ + parameterWithBreaks?: unknown; + /** + * Testing expression placeholders in string: ${expression} should work + */ + parameterWithExpressionPlaceholders?: unknown; + /** + * Testing quotes in string: 'single quote''' and "double quotes""" should work + */ + parameterWithQuotes?: unknown; + /** + * Testing reserved characters in string: * inline * and ** inline ** should work + */ + parameterWithReservedCharacters?: unknown; + /** + * Testing slashes in string: \backwards\\\ and /forwards/// should work + */ + parameterWithSlashes?: unknown; +}; + +export type DeprecatedCallData = { + /** + * This parameter is deprecated + * @deprecated + */ + parameter: (DeprecatedModel) | null; +}; + +export type CallWithParametersData = { + /** + * This is the parameter that goes into the query params + */ + cursor: (string) | null; + fooAllOfEnum: (ModelWithNestedArrayEnumsDataFoo); + fooRefEnum?: ModelWithNestedArrayEnumsDataFoo; + /** + * This is the parameter that goes into the cookie + */ + parameterCookie: (string) | null; + /** + * This is the parameter that goes into the header + */ + parameterHeader: (string) | null; + /** + * This is the parameter that goes into the path + */ + parameterPath: (string) | null; + /** + * This is the parameter that goes into the body + */ + requestBody: { + [key: string]: unknown; + } | null; +}; + +export type CallWithWeirdParameterNamesData = { + /** + * This is the parameter with a reserved keyword + */ + _default?: string; + /** + * This is the parameter that goes into the cookie + */ + parameterCookie: (string) | null; + /** + * This is the parameter that goes into the request header + */ + parameterHeader: (string) | null; + /** + * This is the parameter that goes into the path + */ + parameterPath1?: string; + /** + * This is the parameter that goes into the path + */ + parameterPath2?: string; + /** + * This is the parameter that goes into the path + */ + parameterPath3?: string; + /** + * This is the parameter that goes into the request query params + */ + parameterQuery: (string) | null; + /** + * This is the parameter that goes into the body + */ + requestBody: (ModelWithString) | null; +}; + +export type GetCallWithOptionalParamData = { + /** + * This is an optional parameter + */ + page?: number; + /** + * This is a required parameter + */ + requestBody: ModelWithOneOfEnum; +}; + +export type PostCallWithOptionalParamData = { + /** + * This is a required parameter + */ + parameter: Pageable; + /** + * This is an optional parameter + */ + requestBody?: { + offset?: (number) | null; + }; +}; + +export type PostCallWithOptionalParamResponse = (number | void); + +export type PostApiRequestBodyData = { + /** + * A reusable request body + */ + foo?: ModelWithString; + /** + * This is a reusable parameter + */ + parameter?: string; +}; + +export type PostApiFormDataData = { + /** + * A reusable request body + */ + formData?: ModelWithString; + /** + * This is a reusable parameter + */ + parameter?: string; +}; + +export type CallWithDefaultParametersData = { + /** + * This is a simple boolean with default value + */ + parameterBoolean?: (boolean) | null; + /** + * This is a simple enum with default value + */ + parameterEnum?: 'Success' | 'Warning' | 'Error'; + /** + * This is a simple model with default value + */ + parameterModel?: (ModelWithString) | null; + /** + * This is a simple number with default value + */ + parameterNumber?: (number) | null; + /** + * This is a simple string with default value + */ + parameterString?: (string) | null; +}; + +export type CallWithDefaultOptionalParametersData = { + /** + * This is a simple boolean that is optional with default value + */ + parameterBoolean?: boolean; + /** + * This is a simple enum that is optional with default value + */ + parameterEnum?: 'Success' | 'Warning' | 'Error'; + /** + * This is a simple model that is optional with default value + */ + parameterModel?: ModelWithString; + /** + * This is a simple number that is optional with default value + */ + parameterNumber?: number; + /** + * This is a simple string that is optional with default value + */ + parameterString?: string; +}; + +export type CallToTestOrderOfParamsData = { + /** + * This is a optional string with default + */ + parameterOptionalStringWithDefault?: string; + /** + * This is a optional string with empty default + */ + parameterOptionalStringWithEmptyDefault?: string; + /** + * This is a optional string with no default + */ + parameterOptionalStringWithNoDefault?: string; + /** + * This is a string that can be null with default + */ + parameterStringNullableWithDefault?: (string) | null; + /** + * This is a string that can be null with no default + */ + parameterStringNullableWithNoDefault?: (string) | null; + /** + * This is a string with default + */ + parameterStringWithDefault: string; + /** + * This is a string with empty default + */ + parameterStringWithEmptyDefault: string; + /** + * This is a string with no default + */ + parameterStringWithNoDefault: string; +}; + +export type CallWithNoContentResponseResponse = (void); + +export type CallWithResponseAndNoContentResponseResponse = (number | void); + +export type DummyAResponse = (_400); + +export type DummyBResponse = (void); + +export type CallWithResponseResponse = (_import); + +export type CallWithDuplicateResponsesResponse = ((ModelWithBoolean & ModelWithInteger) | ModelWithString); + +export type CallWithResponsesResponse = ({ + readonly '@namespace.string'?: string; + readonly '@namespace.integer'?: number; + readonly value?: Array; +} | ModelThatExtends | ModelThatExtendsExtends); + +export type CollectionFormatData = { + /** + * This is an array parameter that is sent as csv format (comma-separated values) + */ + parameterArrayCsv: Array<(string)> | null; + /** + * This is an array parameter that is sent as multi format (multiple parameter instances) + */ + parameterArrayMulti: Array<(string)> | null; + /** + * This is an array parameter that is sent as pipes format (pipe-separated values) + */ + parameterArrayPipes: Array<(string)> | null; + /** + * This is an array parameter that is sent as ssv format (space-separated values) + */ + parameterArraySsv: Array<(string)> | null; + /** + * This is an array parameter that is sent as tsv format (tab-separated values) + */ + parameterArrayTsv: Array<(string)> | null; +}; + +export type TypesData = { + /** + * This is a number parameter + */ + id?: number; + /** + * This is an array parameter + */ + parameterArray: Array<(string)> | null; + /** + * This is a boolean parameter + */ + parameterBoolean: (boolean) | null; + /** + * This is a dictionary parameter + */ + parameterDictionary: { + [key: string]: unknown; + } | null; + /** + * This is an enum parameter + */ + parameterEnum: ('Success' | 'Warning' | 'Error') | null; + /** + * This is a number parameter + */ + parameterNumber: number; + /** + * This is an object parameter + */ + parameterObject: { + [key: string]: unknown; + } | null; + /** + * This is a string parameter + */ + parameterString: (string) | null; +}; + +export type TypesResponse = (number | string | boolean | { + [key: string]: unknown; +}); + +export type UploadFileData = { + formData: (Blob | File); +}; + +export type UploadFileResponse = (boolean); + +export type FileResponseData = { + id: string; +}; + +export type FileResponseResponse = ((Blob | File)); + +export type ComplexTypesData = { + /** + * Parameter containing object + */ + parameterObject: { + first?: { + second?: { + third?: string; + }; + }; + }; + /** + * Parameter containing reference + */ + parameterReference: ModelWithString; +}; + +export type ComplexTypesResponse = (Array); + +export type MultipartRequestData = { + formData?: { + content?: (Blob | File); + data?: ((ModelWithString) | null); + }; +}; + +export type MultipartResponseResponse = ({ + file?: (Blob | File); + metadata?: { + foo?: string; + bar?: string; + }; +}); + +export type ComplexParamsData = { + id: number; + requestBody?: { + readonly key: (string) | null; + name: (string) | null; + enabled?: boolean; + readonly type: 'Monkey' | 'Horse' | 'Bird'; + listOfModels?: Array | null; + listOfStrings?: Array<(string)> | null; + parameters: (ModelWithString | ModelWithEnum | ModelWithArray | ModelWithDictionary); + readonly user?: { + readonly id?: number; + readonly name?: (string) | null; + }; + }; +}; + +export type ComplexParamsResponse = (ModelWithString); + +export type CallWithResultFromHeaderResponse = (string); + +export type TestErrorCodeData = { + /** + * Status code to return + */ + status: number; +}; + +export type TestErrorCodeResponse = (unknown); + +export type NonAsciiæøåÆøÅöôêÊ字符串Data = { + /** + * Dummy input param + */ + nonAsciiParamæøåÆøÅöôêÊ: number; +}; + +export type NonAsciiæøåÆøÅöôêÊ字符串Response = (Array); + +export type PutWithFormUrlEncodedData = { + formData: ArrayWithStrings; +}; \ No newline at end of file diff --git a/packages/openapi-ts/test/__snapshots__/test/generated/v3_angular_tree_shakeable_transform/core/ApiError.ts.snap b/packages/openapi-ts/test/__snapshots__/test/generated/v3_angular_tree_shakeable_transform/core/ApiError.ts.snap new file mode 100644 index 000000000..36675d288 --- /dev/null +++ b/packages/openapi-ts/test/__snapshots__/test/generated/v3_angular_tree_shakeable_transform/core/ApiError.ts.snap @@ -0,0 +1,21 @@ +import type { ApiRequestOptions } from './ApiRequestOptions'; +import type { ApiResult } from './ApiResult'; + +export class ApiError extends Error { + public readonly url: string; + public readonly status: number; + public readonly statusText: string; + public readonly body: unknown; + public readonly request: ApiRequestOptions; + + constructor(request: ApiRequestOptions, response: ApiResult, message: string) { + super(message); + + this.name = 'ApiError'; + this.url = response.url; + this.status = response.status; + this.statusText = response.statusText; + this.body = response.body; + this.request = request; + } +} \ No newline at end of file diff --git a/packages/openapi-ts/test/__snapshots__/test/generated/v3_angular_tree_shakeable_transform/core/ApiRequestOptions.ts.snap b/packages/openapi-ts/test/__snapshots__/test/generated/v3_angular_tree_shakeable_transform/core/ApiRequestOptions.ts.snap new file mode 100644 index 000000000..939a0aa4c --- /dev/null +++ b/packages/openapi-ts/test/__snapshots__/test/generated/v3_angular_tree_shakeable_transform/core/ApiRequestOptions.ts.snap @@ -0,0 +1,21 @@ +export type ApiRequestOptions = { + readonly body?: any; + readonly cookies?: Record; + readonly errors?: Record; + readonly formData?: Record | any[] | Blob | File; + readonly headers?: Record; + readonly mediaType?: string; + readonly method: + | 'DELETE' + | 'GET' + | 'HEAD' + | 'OPTIONS' + | 'PATCH' + | 'POST' + | 'PUT'; + readonly path?: Record; + readonly query?: Record; + readonly responseHeader?: string; + readonly responseTransformer?: (data: unknown) => Promise; + readonly url: string; +}; \ No newline at end of file diff --git a/packages/openapi-ts/test/__snapshots__/test/generated/v3_angular_tree_shakeable_transform/core/ApiResult.ts.snap b/packages/openapi-ts/test/__snapshots__/test/generated/v3_angular_tree_shakeable_transform/core/ApiResult.ts.snap new file mode 100644 index 000000000..4c58e3913 --- /dev/null +++ b/packages/openapi-ts/test/__snapshots__/test/generated/v3_angular_tree_shakeable_transform/core/ApiResult.ts.snap @@ -0,0 +1,7 @@ +export type ApiResult = { + readonly body: TData; + readonly ok: boolean; + readonly status: number; + readonly statusText: string; + readonly url: string; +}; \ No newline at end of file diff --git a/packages/openapi-ts/test/__snapshots__/test/generated/v3_angular_tree_shakeable_transform/core/OpenAPI.ts.snap b/packages/openapi-ts/test/__snapshots__/test/generated/v3_angular_tree_shakeable_transform/core/OpenAPI.ts.snap new file mode 100644 index 000000000..9fafe8d52 --- /dev/null +++ b/packages/openapi-ts/test/__snapshots__/test/generated/v3_angular_tree_shakeable_transform/core/OpenAPI.ts.snap @@ -0,0 +1,55 @@ +import type { HttpResponse } from '@angular/common/http'; +import type { ApiRequestOptions } from './ApiRequestOptions'; + +type Headers = Record; +type Middleware = (value: T) => T | Promise; +type Resolver = (options: ApiRequestOptions) => Promise; + +export class Interceptors { + _fns: Middleware[]; + + constructor() { + this._fns = []; + } + + eject(fn: Middleware): void { + const index = this._fns.indexOf(fn); + if (index !== -1) { + this._fns = [...this._fns.slice(0, index), ...this._fns.slice(index + 1)]; + } + } + + use(fn: Middleware): void { + this._fns = [...this._fns, fn]; + } +} + +export type OpenAPIConfig = { + BASE: string; + CREDENTIALS: 'include' | 'omit' | 'same-origin'; + ENCODE_PATH?: ((path: string) => string) | undefined; + HEADERS?: Headers | Resolver | undefined; + PASSWORD?: string | Resolver | undefined; + TOKEN?: string | Resolver | undefined; + USERNAME?: string | Resolver | undefined; + VERSION: string; + WITH_CREDENTIALS: boolean; + interceptors: { + response: Interceptors>; + }; +}; + +export const OpenAPI: OpenAPIConfig = { + BASE: 'http://localhost:3000/base', + CREDENTIALS: 'include', + ENCODE_PATH: undefined, + HEADERS: undefined, + PASSWORD: undefined, + TOKEN: undefined, + USERNAME: undefined, + VERSION: '1.0', + WITH_CREDENTIALS: false, + interceptors: { + response: new Interceptors(), + }, +}; \ No newline at end of file diff --git a/packages/openapi-ts/test/__snapshots__/test/generated/v3_angular_tree_shakeable_transform/core/request.ts.snap b/packages/openapi-ts/test/__snapshots__/test/generated/v3_angular_tree_shakeable_transform/core/request.ts.snap new file mode 100644 index 000000000..4eab34ab6 --- /dev/null +++ b/packages/openapi-ts/test/__snapshots__/test/generated/v3_angular_tree_shakeable_transform/core/request.ts.snap @@ -0,0 +1,337 @@ +import { HttpClient, HttpHeaders } from '@angular/common/http'; +import type { HttpResponse, HttpErrorResponse } from '@angular/common/http'; +import { forkJoin, of, throwError } from 'rxjs'; +import { catchError, map, switchMap } from 'rxjs/operators'; +import type { Observable } from 'rxjs'; + +import { ApiError } from './ApiError'; +import type { ApiRequestOptions } from './ApiRequestOptions'; +import type { ApiResult } from './ApiResult'; +import type { OpenAPIConfig } from './OpenAPI'; + +export const isString = (value: unknown): value is string => { + return typeof value === 'string'; +}; + +export const isStringWithValue = (value: unknown): value is string => { + return isString(value) && value !== ''; +}; + +export const isBlob = (value: any): value is Blob => { + return value instanceof Blob; +}; + +export const isFormData = (value: unknown): value is FormData => { + return value instanceof FormData; +}; + +export const base64 = (str: string): string => { + try { + return btoa(str); + } catch (err) { + // @ts-ignore + return Buffer.from(str).toString('base64'); + } +}; + +export const getQueryString = (params: Record): string => { + const qs: string[] = []; + + const append = (key: string, value: unknown) => { + qs.push(`${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`); + }; + + const encodePair = (key: string, value: unknown) => { + if (value === undefined || value === null) { + return; + } + + if (value instanceof Date) { + append(key, value.toISOString()); + } else if (Array.isArray(value)) { + value.forEach(v => encodePair(key, v)); + } else if (typeof value === 'object') { + Object.entries(value).forEach(([k, v]) => encodePair(`${key}[${k}]`, v)); + } else { + append(key, value); + } + }; + + Object.entries(params).forEach(([key, value]) => encodePair(key, value)); + + return qs.length ? `?${qs.join('&')}` : ''; +}; + +const getUrl = (config: OpenAPIConfig, options: ApiRequestOptions): string => { + const encoder = config.ENCODE_PATH || encodeURI; + + const path = options.url + .replace('{api-version}', config.VERSION) + .replace(/{(.*?)}/g, (substring: string, group: string) => { + if (options.path?.hasOwnProperty(group)) { + return encoder(String(options.path[group])); + } + return substring; + }); + + const url = config.BASE + path; + return options.query ? url + getQueryString(options.query) : url; +}; + +export const getFormData = (options: ApiRequestOptions): FormData | undefined => { + if (options.formData) { + const formData = new FormData(); + + const process = (key: string, value: unknown) => { + if (isString(value) || isBlob(value)) { + formData.append(key, value); + } else { + formData.append(key, JSON.stringify(value)); + } + }; + + Object.entries(options.formData) + .filter(([, value]) => value !== undefined && value !== null) + .forEach(([key, value]) => { + if (Array.isArray(value)) { + value.forEach(v => process(key, v)); + } else { + process(key, value); + } + }); + + return formData; + } + return undefined; +}; + +type Resolver = (options: ApiRequestOptions) => Promise; + +export const resolve = async (options: ApiRequestOptions, resolver?: T | Resolver): Promise => { + if (typeof resolver === 'function') { + return (resolver as Resolver)(options); + } + return resolver; +}; + +export const getHeaders = (config: OpenAPIConfig, options: ApiRequestOptions): Observable => { + return forkJoin({ + // @ts-ignore + token: resolve(options, config.TOKEN), + // @ts-ignore + username: resolve(options, config.USERNAME), + // @ts-ignore + password: resolve(options, config.PASSWORD), + // @ts-ignore + additionalHeaders: resolve(options, config.HEADERS), + }).pipe( + map(({ token, username, password, additionalHeaders }) => { + const headers = Object.entries({ + Accept: 'application/json', + ...additionalHeaders, + ...options.headers, + }) + .filter(([, value]) => value !== undefined && value !== null) + .reduce((headers, [key, value]) => ({ + ...headers, + [key]: String(value), + }), {} as Record); + + if (isStringWithValue(token)) { + headers['Authorization'] = `Bearer ${token}`; + } + + if (isStringWithValue(username) && isStringWithValue(password)) { + const credentials = base64(`${username}:${password}`); + headers['Authorization'] = `Basic ${credentials}`; + } + + if (options.body !== undefined) { + if (options.mediaType) { + headers['Content-Type'] = options.mediaType; + } else if (isBlob(options.body)) { + headers['Content-Type'] = options.body.type || 'application/octet-stream'; + } else if (isString(options.body)) { + headers['Content-Type'] = 'text/plain'; + } else if (!isFormData(options.body)) { + headers['Content-Type'] = 'application/json'; + } + } + + return new HttpHeaders(headers); + }), + ); +}; + +export const getRequestBody = (options: ApiRequestOptions): unknown => { + if (options.body) { + if (options.mediaType?.includes('application/json') || options.mediaType?.includes('+json')) { + return JSON.stringify(options.body); + } else if (isString(options.body) || isBlob(options.body) || isFormData(options.body)) { + return options.body; + } else { + return JSON.stringify(options.body); + } + } + return undefined; +}; + +export const sendRequest = ( + config: OpenAPIConfig, + options: ApiRequestOptions, + http: HttpClient, + url: string, + body: unknown, + formData: FormData | undefined, + headers: HttpHeaders +): Observable> => { + return http.request(options.method, url, { + headers, + body: body ?? formData, + withCredentials: config.WITH_CREDENTIALS, + observe: 'response', + }); +}; + +export const getResponseHeader = (response: HttpResponse, responseHeader?: string): string | undefined => { + if (responseHeader) { + const value = response.headers.get(responseHeader); + if (isString(value)) { + return value; + } + } + return undefined; +}; + +export const getResponseBody = (response: HttpResponse): T | undefined => { + if (response.status !== 204 && response.body !== null) { + return response.body; + } + return undefined; +}; + +export const catchErrorCodes = (options: ApiRequestOptions, result: ApiResult): void => { + const errors: Record = { + 400: 'Bad Request', + 401: 'Unauthorized', + 402: 'Payment Required', + 403: 'Forbidden', + 404: 'Not Found', + 405: 'Method Not Allowed', + 406: 'Not Acceptable', + 407: 'Proxy Authentication Required', + 408: 'Request Timeout', + 409: 'Conflict', + 410: 'Gone', + 411: 'Length Required', + 412: 'Precondition Failed', + 413: 'Payload Too Large', + 414: 'URI Too Long', + 415: 'Unsupported Media Type', + 416: 'Range Not Satisfiable', + 417: 'Expectation Failed', + 418: 'Im a teapot', + 421: 'Misdirected Request', + 422: 'Unprocessable Content', + 423: 'Locked', + 424: 'Failed Dependency', + 425: 'Too Early', + 426: 'Upgrade Required', + 428: 'Precondition Required', + 429: 'Too Many Requests', + 431: 'Request Header Fields Too Large', + 451: 'Unavailable For Legal Reasons', + 500: 'Internal Server Error', + 501: 'Not Implemented', + 502: 'Bad Gateway', + 503: 'Service Unavailable', + 504: 'Gateway Timeout', + 505: 'HTTP Version Not Supported', + 506: 'Variant Also Negotiates', + 507: 'Insufficient Storage', + 508: 'Loop Detected', + 510: 'Not Extended', + 511: 'Network Authentication Required', + ...options.errors, + } + + const error = errors[result.status]; + if (error) { + throw new ApiError(options, result, error); + } + + if (!result.ok) { + const errorStatus = result.status ?? 'unknown'; + const errorStatusText = result.statusText ?? 'unknown'; + const errorBody = (() => { + try { + return JSON.stringify(result.body, null, 2); + } catch (e) { + return undefined; + } + })(); + + throw new ApiError(options, result, + `Generic Error: status: ${errorStatus}; status text: ${errorStatusText}; body: ${errorBody}` + ); + } +}; + +/** + * Request method + * @param config The OpenAPI configuration object + * @param http The Angular HTTP client + * @param options The request options from the service + * @returns Observable + * @throws ApiError + */ +export const request = (config: OpenAPIConfig, http: HttpClient, options: ApiRequestOptions): Observable => { + const url = getUrl(config, options); + const formData = getFormData(options); + const body = getRequestBody(options); + + return getHeaders(config, options).pipe( + switchMap(headers => { + return sendRequest(config, options, http, url, body, formData, headers); + }), + switchMap(async response => { + for (const fn of config.interceptors.response._fns) { + response = await fn(response); + } + const responseBody = getResponseBody(response); + const responseHeader = getResponseHeader(response, options.responseHeader); + + let transformedBody = responseBody; + if (options.responseTransformer && response.ok) { + transformedBody = await options.responseTransformer(responseBody) + } + + return { + url, + ok: response.ok, + status: response.status, + statusText: response.statusText, + body: responseHeader ?? transformedBody, + } as ApiResult; + }), + catchError((error: HttpErrorResponse) => { + if (!error.status) { + return throwError(() => error); + } + return of({ + url, + ok: error.ok, + status: error.status, + statusText: error.statusText, + body: error.error ?? error.statusText, + } as ApiResult); + }), + map(result => { + catchErrorCodes(options, result); + return result.body as T; + }), + catchError((error: ApiError) => { + return throwError(() => error); + }), + ); +}; \ No newline at end of file diff --git a/packages/openapi-ts/test/__snapshots__/test/generated/v3_angular_tree_shakeable_transform/index.ts.snap b/packages/openapi-ts/test/__snapshots__/test/generated/v3_angular_tree_shakeable_transform/index.ts.snap new file mode 100644 index 000000000..d23426032 --- /dev/null +++ b/packages/openapi-ts/test/__snapshots__/test/generated/v3_angular_tree_shakeable_transform/index.ts.snap @@ -0,0 +1,5 @@ +// This file is auto-generated by @hey-api/openapi-ts +export { ApiError } from './core/ApiError'; +export { OpenAPI, type OpenAPIConfig } from './core/OpenAPI'; +export * from './services.gen'; +export * from './types.gen'; \ No newline at end of file diff --git a/packages/openapi-ts/test/__snapshots__/test/generated/v3_angular_tree_shakeable_transform/services.gen.ts.snap b/packages/openapi-ts/test/__snapshots__/test/generated/v3_angular_tree_shakeable_transform/services.gen.ts.snap new file mode 100644 index 000000000..7f3a5a45b --- /dev/null +++ b/packages/openapi-ts/test/__snapshots__/test/generated/v3_angular_tree_shakeable_transform/services.gen.ts.snap @@ -0,0 +1,67 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import type { Observable } from 'rxjs'; +import { OpenAPI } from './core/OpenAPI'; +import { request as __request } from './core/request'; +import { type ParentModelWithDatesResponse, type ModelWithDatesResponse, type ModelWithDatesArrayResponse, type ArrayOfDatesResponse, type DateResponse, type MultipleResponsesResponse, ParentModelWithDatesResponseTransformer, ModelWithDatesResponseTransformer, ModelWithDatesArrayResponseTransformer } from './types.gen'; + +/** + * @returns ParentModelWithDates Success + * @returns unknown Success + * @throws ApiError + */ +export const parentModelWithDates = function (): Observable { return __request(OpenAPI, this.http, { + method: 'POST', + url: '/api/model-with-dates', + responseTransformer: ParentModelWithDatesResponseTransformer +}); }; + +/** + * @returns ModelWithDates Success + * @throws ApiError + */ +export const modelWithDates = function (): Observable { return __request(OpenAPI, this.http, { + method: 'PUT', + url: '/api/model-with-dates', + responseTransformer: ModelWithDatesResponseTransformer +}); }; + +/** + * @returns ModelWithDates Success + * @throws ApiError + */ +export const modelWithDatesArray = function (): Observable { return __request(OpenAPI, this.http, { + method: 'PUT', + url: '/api/model-with-dates-array', + responseTransformer: ModelWithDatesArrayResponseTransformer +}); }; + +/** + * @returns string Success + * @throws ApiError + */ +export const arrayOfDates = function (): Observable { return __request(OpenAPI, this.http, { + method: 'PUT', + url: '/api/array-of-dates' +}); }; + +/** + * @returns string Success + * @throws ApiError + */ +export const date = function (): Observable { return __request(OpenAPI, this.http, { + method: 'PUT', + url: '/api/date' +}); }; + +/** + * @returns ModelWithDates Updated + * @returns SimpleModel Created + * @throws ApiError + */ +export const multipleResponses = function (): Observable { return __request(OpenAPI, this.http, { + method: 'PUT', + url: '/api/multiple-responses' +}); }; \ No newline at end of file diff --git a/packages/openapi-ts/test/__snapshots__/test/generated/v3_angular_tree_shakeable_transform/types.gen.ts.snap b/packages/openapi-ts/test/__snapshots__/test/generated/v3_angular_tree_shakeable_transform/types.gen.ts.snap new file mode 100644 index 000000000..1be361475 --- /dev/null +++ b/packages/openapi-ts/test/__snapshots__/test/generated/v3_angular_tree_shakeable_transform/types.gen.ts.snap @@ -0,0 +1,102 @@ +// This file is auto-generated by @hey-api/openapi-ts + +/** + * This is a model that contains a some dates + */ +export type SimpleModel = { + id: number; + name: string; + readonly enabled: boolean; +}; + +/** + * This is a model that contains a some dates + */ +export type ModelWithDates = { + id: number; + name: string; + readonly enabled: boolean; + readonly modified: Date; + readonly expires?: Date; +}; + +/** + * This is a model that contains a some dates and arrays + */ +export type ParentModelWithDates = { + id: number; + readonly modified?: Date; + items?: Array; + item?: ModelWithDates; + simpleItems?: Array; + simpleItem?: SimpleModel; + dates?: Array<(Date)>; + strings?: Array<(string)>; +}; + +export type ParentModelWithDatesResponse = (ParentModelWithDates | unknown); + +export type ModelWithDatesResponse = (ModelWithDates); + +export type ModelWithDatesArrayResponse = (Array); + +export type ArrayOfDatesResponse = (Array<(Date)>); + +export type DateResponse = (Date); + +export type MultipleResponsesResponse = (Array | Array); + +export type ParentModelWithDatesResponseTransformer = (data: any) => Promise; + +export type ParentModelWithDatesModelResponseTransformer = (data: any) => ParentModelWithDates; + +export type ModelWithDatesModelResponseTransformer = (data: any) => ModelWithDates; + +export const ModelWithDatesModelResponseTransformer: ModelWithDatesModelResponseTransformer = data => { + if (data?.modified) { + data.modified = new Date(data.modified); + } + if (data?.expires) { + data.expires = new Date(data.expires); + } + return data; +}; + +export const ParentModelWithDatesModelResponseTransformer: ParentModelWithDatesModelResponseTransformer = data => { + if (data?.modified) { + data.modified = new Date(data.modified); + } + if (Array.isArray(data?.items)) { + data.items.forEach(ModelWithDatesModelResponseTransformer); + } + if (data?.item) { + ModelWithDatesModelResponseTransformer(data.item); + } + if (Array.isArray(data?.dates)) { + data.dates = data.dates.map(item => new Date(item)); + } + return data; +}; + +export const ParentModelWithDatesResponseTransformer: ParentModelWithDatesResponseTransformer = async (data) => { + if (data) { + ParentModelWithDatesModelResponseTransformer(data); + } + return data; +}; + +export type ModelWithDatesResponseTransformer = (data: any) => Promise; + +export const ModelWithDatesResponseTransformer: ModelWithDatesResponseTransformer = async (data) => { + ModelWithDatesModelResponseTransformer(data); + return data; +}; + +export type ModelWithDatesArrayResponseTransformer = (data: any) => Promise; + +export const ModelWithDatesArrayResponseTransformer: ModelWithDatesArrayResponseTransformer = async (data) => { + if (Array.isArray(data)) { + data.forEach(ModelWithDatesModelResponseTransformer); + } + return data; +}; \ No newline at end of file diff --git a/packages/openapi-ts/test/index.spec.ts b/packages/openapi-ts/test/index.spec.ts index 86ffef8f3..1dc720365 100644 --- a/packages/openapi-ts/test/index.spec.ts +++ b/packages/openapi-ts/test/index.spec.ts @@ -90,6 +90,14 @@ describe('OpenAPI v3', () => { description: 'generate angular client', name: 'v3_angular', }, + { + config: createConfig({ + client: 'angular', + types: {}, + }), + description: 'generate tree-shakeable angular client', + name: 'v3_angular_tree_shakeable', + }, { config: createConfig({ client: 'node',