diff --git a/.changeset/clever-pants-joke.md b/.changeset/clever-pants-joke.md new file mode 100644 index 000000000..82584ff4a --- /dev/null +++ b/.changeset/clever-pants-joke.md @@ -0,0 +1,5 @@ +--- +"@hey-api/openapi-ts": patch +--- + +fix async response interceptors when using angular client diff --git a/.changeset/spotty-apricots-learn.md b/.changeset/spotty-apricots-learn.md new file mode 100644 index 000000000..3341e3442 --- /dev/null +++ b/.changeset/spotty-apricots-learn.md @@ -0,0 +1,5 @@ +--- +"@hey-api/openapi-ts": patch +--- + +fix deprecation warning on `throwError` in Angular client diff --git a/.changeset/stale-deers-build.md b/.changeset/stale-deers-build.md new file mode 100644 index 000000000..2d5716757 --- /dev/null +++ b/.changeset/stale-deers-build.md @@ -0,0 +1,5 @@ +--- +"@hey-api/openapi-ts": patch +--- + +Do not create or export CancelablePromise when using Angular client diff --git a/.changeset/witty-ladybugs-sneeze.md b/.changeset/witty-ladybugs-sneeze.md new file mode 100644 index 000000000..728676de3 --- /dev/null +++ b/.changeset/witty-ladybugs-sneeze.md @@ -0,0 +1,5 @@ +--- +"@hey-api/openapi-ts": patch +--- + +fix providing interceptors in Angular client diff --git a/README.md b/README.md index e52e1b407..b937d23c5 100644 --- a/README.md +++ b/README.md @@ -216,7 +216,7 @@ OpenAPI.interceptors.response.use(async (response) => { If you need to remove an interceptor, pass the same function to `OpenAPI.interceptors.request.eject()` or `OpenAPI.interceptors.response.eject()`. -> ⚠️ Angular client does not currently support request interceptors and async response interceptors. +> ⚠️ Angular client does not currently support request interceptors. ## Migrating diff --git a/src/templates/client.hbs b/src/templates/client.hbs index 0eeab630f..9fbb50988 100644 --- a/src/templates/client.hbs +++ b/src/templates/client.hbs @@ -6,6 +6,7 @@ import { AngularHttpRequest } from './core/AngularHttpRequest'; import { BaseHttpRequest } from './core/BaseHttpRequest'; import type { OpenAPIConfig } from './core/OpenAPI'; import { OpenAPI } from './core/OpenAPI'; +import { Interceptors } from './core/OpenAPI'; {{else}} import type { BaseHttpRequest } from './core/BaseHttpRequest'; import type { OpenAPIConfig } from './core/OpenAPI'; @@ -35,6 +36,9 @@ import { {{{name}}}{{{@root.$config.postfixServices}}} } from './services/{{{nam PASSWORD: OpenAPI?.PASSWORD, HEADERS: OpenAPI?.HEADERS, ENCODE_PATH: OpenAPI?.ENCODE_PATH, + interceptors: { + response: new Interceptors(), + }, } as OpenAPIConfig, }, { diff --git a/src/templates/core/OpenAPI.hbs b/src/templates/core/OpenAPI.hbs index 96bf6dec2..e0be0bf3f 100644 --- a/src/templates/core/OpenAPI.hbs +++ b/src/templates/core/OpenAPI.hbs @@ -20,11 +20,7 @@ import type { TResult } from './types'; {{/if}} type Headers = Record; -{{#equals @root.$config.client 'angular'}} -type Middleware = (value: T) => T; -{{else}} type Middleware = (value: T) => T | Promise; -{{/equals}} type Resolver = (options: ApiRequestOptions) => Promise; export class Interceptors { diff --git a/src/templates/core/angular/request.hbs b/src/templates/core/angular/request.hbs index 637a1daa4..5c17f586e 100644 --- a/src/templates/core/angular/request.hbs +++ b/src/templates/core/angular/request.hbs @@ -71,9 +71,9 @@ export const request = (config: OpenAPIConfig, http: HttpClient, options: Api switchMap(headers => { return sendRequest(config, options, http, url, body, formData, headers); }), - map(response => { + switchMap(async response => { for (const fn of config.interceptors.response._fns) { - response = fn(response) + response = await fn(response) } const responseBody = getResponseBody(response); const responseHeader = getResponseHeader(response, options.responseHeader); @@ -87,7 +87,7 @@ export const request = (config: OpenAPIConfig, http: HttpClient, options: Api }), catchError((error: HttpErrorResponse) => { if (!error.status) { - return throwError(error); + return throwError(() => error); } return of({ url, @@ -102,7 +102,7 @@ export const request = (config: OpenAPIConfig, http: HttpClient, options: Api return result.body as T; }), catchError((error: ApiError) => { - return throwError(error); + return throwError(() => error); }), ); }; diff --git a/src/templates/index.hbs b/src/templates/index.hbs index 23a9d9797..6b5578f8d 100644 --- a/src/templates/index.hbs +++ b/src/templates/index.hbs @@ -7,7 +7,9 @@ export { ApiError } from './core/ApiError'; {{#if @root.$config.name}} export { BaseHttpRequest } from './core/BaseHttpRequest'; {{/if}} +{{#notEquals @root.$config.client 'angular'}} export { CancelablePromise, CancelError } from './core/CancelablePromise'; +{{/notEquals}} export { OpenAPI } from './core/OpenAPI'; export type { OpenAPIConfig } from './core/OpenAPI'; {{/if}} diff --git a/src/utils/write/core.ts b/src/utils/write/core.ts index 4a9f26709..ea563280f 100644 --- a/src/utils/write/core.ts +++ b/src/utils/write/core.ts @@ -53,13 +53,15 @@ export const writeClientCore = async ( ...context, }) ); - await writeFileSync( - path.resolve(outputPath, 'CancelablePromise.ts'), - templates.core.cancelablePromise({ - $config: config, - ...context, - }) - ); + if (config.client !== 'angular') { + await writeFileSync( + path.resolve(outputPath, 'CancelablePromise.ts'), + templates.core.cancelablePromise({ + $config: config, + ...context, + }) + ); + } await writeFileSync( path.resolve(outputPath, 'request.ts'), templates.core.request({ diff --git a/test/__snapshots__/v3_angular/core/ApiError.ts.snap b/test/__snapshots__/v3_angular/core/ApiError.ts.snap new file mode 100644 index 000000000..2c11b4136 --- /dev/null +++ b/test/__snapshots__/v3_angular/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; + } +} diff --git a/test/__snapshots__/v3_angular/core/ApiRequestOptions.ts.snap b/test/__snapshots__/v3_angular/core/ApiRequestOptions.ts.snap new file mode 100644 index 000000000..e93003ee7 --- /dev/null +++ b/test/__snapshots__/v3_angular/core/ApiRequestOptions.ts.snap @@ -0,0 +1,13 @@ +export type ApiRequestOptions = { + readonly method: 'GET' | 'PUT' | 'POST' | 'DELETE' | 'OPTIONS' | 'HEAD' | 'PATCH'; + readonly url: string; + readonly path?: Record; + readonly cookies?: Record; + readonly headers?: Record; + readonly query?: Record; + readonly formData?: Record; + readonly body?: any; + readonly mediaType?: string; + readonly responseHeader?: string; + readonly errors?: Record; +}; diff --git a/test/__snapshots__/v3_angular/core/ApiResult.ts.snap b/test/__snapshots__/v3_angular/core/ApiResult.ts.snap new file mode 100644 index 000000000..caa79c2ea --- /dev/null +++ b/test/__snapshots__/v3_angular/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; +}; diff --git a/test/__snapshots__/v3_angular/core/OpenAPI.ts.snap b/test/__snapshots__/v3_angular/core/OpenAPI.ts.snap new file mode 100644 index 000000000..fe8ce64ed --- /dev/null +++ b/test/__snapshots__/v3_angular/core/OpenAPI.ts.snap @@ -0,0 +1,54 @@ +import type { HttpResponse } from '@angular/common/http'; +import type { ApiRequestOptions } from './ApiRequestOptions'; +import type { TResult } from './types'; + +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) { + const index = this._fns.indexOf(fn); + if (index !== -1) { + this._fns = [...this._fns.slice(0, index), ...this._fns.slice(index + 1)]; + } + } + + use(fn: Middleware) { + 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; + RESULT?: TResult; + 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, + RESULT: 'body', + TOKEN: undefined, + USERNAME: undefined, + VERSION: '1.0', + WITH_CREDENTIALS: false, + interceptors: { response: new Interceptors() }, +}; diff --git a/test/__snapshots__/v3_angular/core/request.ts.snap b/test/__snapshots__/v3_angular/core/request.ts.snap new file mode 100644 index 000000000..b7553394d --- /dev/null +++ b/test/__snapshots__/v3_angular/core/request.ts.snap @@ -0,0 +1,307 @@ +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 !== null && + typeof value === 'object' && + typeof value.type === 'string' && + typeof value.stream === 'function' && + typeof value.arrayBuffer === 'function' && + typeof value.constructor === 'function' && + typeof value.constructor.name === 'string' && + /^(Blob|File)$/.test(value.constructor.name) && + /^(Blob|File)$/.test(value[Symbol.toStringTag]) + ); +}; + +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 (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({ + token: resolve(options, config.TOKEN), + username: resolve(options, config.USERNAME), + password: resolve(options, config.PASSWORD), + 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('/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', + 403: 'Forbidden', + 404: 'Not Found', + 500: 'Internal Server Error', + 502: 'Bad Gateway', + 503: 'Service Unavailable', + ...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); + return { + url, + ok: response.ok, + status: response.status, + statusText: response.statusText, + body: responseHeader ?? responseBody, + } 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); + }) + ); +}; diff --git a/test/__snapshots__/v3_angular/core/types.ts.snap b/test/__snapshots__/v3_angular/core/types.ts.snap new file mode 100644 index 000000000..e33a5d996 --- /dev/null +++ b/test/__snapshots__/v3_angular/core/types.ts.snap @@ -0,0 +1,10 @@ +import type { ApiResult } from './ApiResult'; + +export type TResult = 'body' | 'raw'; + +export type TApiResponse = + Exclude extends never ? ApiResult : ApiResult['body']; + +export type TConfig = { + _result?: T; +}; diff --git a/test/__snapshots__/v3_angular/index.ts.snap b/test/__snapshots__/v3_angular/index.ts.snap new file mode 100644 index 000000000..85738ded0 --- /dev/null +++ b/test/__snapshots__/v3_angular/index.ts.snap @@ -0,0 +1,7 @@ +export { ApiError } from './core/ApiError'; +export { OpenAPI } from './core/OpenAPI'; +export type { OpenAPIConfig } from './core/OpenAPI'; + +export * from './models'; +export * from './schemas'; +export * from './services'; diff --git a/test/__snapshots__/v3_angular/models.ts.snap b/test/__snapshots__/v3_angular/models.ts.snap new file mode 100644 index 000000000..14e52e1c7 --- /dev/null +++ b/test/__snapshots__/v3_angular/models.ts.snap @@ -0,0 +1,705 @@ +/** + * 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 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; + +/** + * 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; + +/** + * This is a simple array with numbers + */ +export type ArrayWithNumbers = Array; + +/** + * This is a simple array with booleans + */ +export type ArrayWithBooleans = Array; + +/** + * This is a simple array with strings + */ +export type ArrayWithStrings = Array; + +/** + * 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<{ + foo?: string; + 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 = Record; + +/** + * This is a string reference + */ +export type DictionaryWithReference = Record; + +/** + * This is a complex dictionary + */ +export type DictionaryWithArray = Record>; + +/** + * This is a string dictionary + */ +export type DictionaryWithDictionary = Record>; + +/** + * This is a complex dictionary + */ +export type DictionaryWithProperties = Record< + 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 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 model with one enum + */ +export type ModelWithEnum = { + /** + * This is a simple enum with strings + */ + test?: '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; +}; + +/** + * This is a model with one enum with escaped name + */ +export type ModelWithEnumWithHyphen = { + '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?: Record; + dictionaryWithEnumFromDescription?: Record; + arrayWithEnum?: Array<'Success' | 'Warning' | 'Error'>; + arrayWithDescription?: Array; +}; + +/** + * 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; + propWithNumber?: Array; +}; + +/** + * This is a model with one property containing an array + */ +export type ModelWithArray = { + prop?: Array; + propWithFile?: Array; + propWithNumber?: Array; +}; + +/** + * This is a model with one property containing a dictionary + */ +export type ModelWithDictionary = { + prop?: Record; +}; + +/** + * 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 | Array; +}; + +export type Enum1 = '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 | 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 | Record; +}; + +/** + * This is a model that contains a dictionary of simple arrays within composition + */ +export type CompositionWithOneOfAndSimpleArrayDictionary = { + propA?: boolean | Record>; +}; + +/** + * This is a model that contains a dictionary of complex arrays (composited) within composition + */ +export type CompositionWithOneOfAndComplexArrayDictionary = { + propA?: boolean | Record>; +}; + +/** + * 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; +}; + +/** + * This is a free-form object without additionalProperties. + */ +export type FreeFormObjectWithoutAdditionalProperties = Record; + +/** + * This is a free-form object with additionalProperties: true. + */ +export type FreeFormObjectWithAdditionalPropertiesEqTrue = Record; + +/** + * This is a free-form object with additionalProperties: {}. + */ +export type FreeFormObjectWithAdditionalPropertiesEqEmptyObject = Record; + +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; +}; + +export type NestedAnyOfArraysNullable = { + nullableArray?: Array | null; +}; + +export type CompositionWithOneOfAndProperties = + | { + foo: SimpleParameter; + baz: number | null; + qux: number; + } + | { + bar: NonAsciiStringæøåÆØÅöôêÊ字符串; + baz: number | null; + qux: number; + }; + +/** + * An object that can be null + */ +export type NullableObject = { + foo?: string; +} | null; + +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 ModelWithNestedArrayEnumsDataFoo = 'foo' | 'bar'; + +export type ModelWithNestedArrayEnumsDataBar = 'baz' | 'qux'; + +export type ModelWithNestedArrayEnumsData = { + foo?: Array; + bar?: Array; +}; + +export type ModelWithNestedArrayEnums = { + array_strings?: Array; + data?: ModelWithNestedArrayEnumsData; +}; + +export type ModelWithNestedCompositionEnums = { + foo?: ModelWithNestedArrayEnumsDataFoo; +}; + +export type ModelWithReadOnlyAndWriteOnly = { + foo: string; + readonly bar: string; + baz: string; +}; + +/** + * This is a reusable parameter + */ +export type SimpleParameter = string; diff --git a/test/__snapshots__/v3_angular/schemas.ts.snap b/test/__snapshots__/v3_angular/schemas.ts.snap new file mode 100644 index 000000000..9876643a6 --- /dev/null +++ b/test/__snapshots__/v3_angular/schemas.ts.snap @@ -0,0 +1,1427 @@ +export const $CommentWithBreaks = { + type: 'number', + description: `Testing multiline comments in string: First line +Second line + +Fourth line`, +} as const; + +export const $CommentWithBackticks = { + type: 'number', + description: `Testing backticks in string: \`backticks\` and \`\`\`multiple backticks\`\`\` should work`, +} as const; + +export const $CommentWithSlashes = { + type: 'number', + description: `Testing slashes in string: \\backwards\\\\\\ and /forwards/// should work`, +} as const; + +export const $CommentWithExpressionPlaceholders = { + type: 'number', + description: `Testing expression placeholders in string: \${expression} should work`, +} as const; + +export const $CommentWithQuotes = { + type: 'number', + description: `Testing quotes in string: 'single quote''' and "double quotes""" should work`, +} as const; + +export const $CommentWithReservedCharacters = { + type: 'number', + description: `Testing reserved characters in string: /* inline */ and /** inline **/ should work`, +} as const; + +export const $SimpleInteger = { + type: 'number', + description: `This is a simple number`, +} as const; + +export const $SimpleBoolean = { + type: 'boolean', + description: `This is a simple boolean`, +} as const; + +export const $SimpleString = { + type: 'string', + description: `This is a simple string`, +} as const; + +export const $NonAsciiStringæøåÆØÅöôêÊ字符串 = { + type: 'string', + description: `A string with non-ascii (unicode) characters valid in typescript identifiers (æøåÆØÅöÔèÈ字符串)`, +} as const; + +export const $SimpleFile = { + type: 'binary', + description: `This is a simple file`, +} as const; + +export const $SimpleReference = { + type: 'ModelWithString', + description: `This is a simple reference`, +} as const; + +export const $SimpleStringWithPattern = { + type: 'string', + description: `This is a simple string`, + isNullable: true, + maxLength: 64, + pattern: '^[a-zA-Z0-9_]*$', +} as const; + +export const $EnumWithStrings = { + type: 'Enum', +} as const; + +export const $EnumWithReplacedCharacters = { + type: 'Enum', +} as const; + +export const $EnumWithNumbers = { + type: 'Enum', +} as const; + +export const $EnumFromDescription = { + type: 'number', + description: `Success=1,Warning=2,Error=3`, +} as const; + +export const $EnumWithExtensions = { + type: 'Enum', +} as const; + +export const $ArrayWithNumbers = { + type: 'array', + contains: { + type: 'number', + }, +} as const; + +export const $ArrayWithBooleans = { + type: 'array', + contains: { + type: 'boolean', + }, +} as const; + +export const $ArrayWithStrings = { + type: 'array', + contains: { + type: 'string', + }, +} as const; + +export const $ArrayWithReferences = { + type: 'array', + contains: { + type: 'ModelWithString', + }, +} as const; + +export const $ArrayWithArray = { + type: 'array', + contains: { + type: 'array', + contains: { + type: 'ModelWithString', + }, + }, +} as const; + +export const $ArrayWithProperties = { + type: 'array', + contains: { + properties: { + foo: { + type: 'string', + }, + bar: { + type: 'string', + }, + }, + }, +} as const; + +export const $ArrayWithAnyOfProperties = { + type: 'array', + contains: { + type: 'any-of', + contains: [ + { + properties: { + foo: { + type: 'string', + }, + }, + }, + { + properties: { + bar: { + type: 'string', + }, + }, + }, + ], + }, +} as const; + +export const $AnyOfAnyAndNull = { + properties: { + data: { + type: 'any-of', + contains: [ + { + properties: {}, + }, + { + type: 'null', + }, + ], + }, + }, +} as const; + +export const $AnyOfArrays = { + description: `This is a simple array with any of properties`, + properties: { + results: { + type: 'array', + contains: { + type: 'any-of', + contains: [ + { + properties: { + foo: { + type: 'string', + }, + }, + }, + { + properties: { + bar: { + type: 'string', + }, + }, + }, + ], + }, + }, + }, +} as const; + +export const $DictionaryWithString = { + type: 'dictionary', + contains: { + type: 'string', + }, +} as const; + +export const $DictionaryWithReference = { + type: 'dictionary', + contains: { + type: 'ModelWithString', + }, +} as const; + +export const $DictionaryWithArray = { + type: 'dictionary', + contains: { + type: 'array', + contains: { + type: 'ModelWithString', + }, + }, +} as const; + +export const $DictionaryWithDictionary = { + type: 'dictionary', + contains: { + type: 'dictionary', + contains: { + type: 'string', + }, + }, +} as const; + +export const $DictionaryWithProperties = { + type: 'dictionary', + contains: { + properties: { + foo: { + type: 'string', + }, + bar: { + type: 'string', + }, + }, + }, +} as const; + +export const $ModelWithInteger = { + description: `This is a model with one number property`, + properties: { + prop: { + type: 'number', + description: `This is a simple number property`, + }, + }, +} as const; + +export const $ModelWithBoolean = { + description: `This is a model with one boolean property`, + properties: { + prop: { + type: 'boolean', + description: `This is a simple boolean property`, + }, + }, +} as const; + +export const $ModelWithString = { + description: `This is a model with one string property`, + properties: { + prop: { + type: 'string', + description: `This is a simple string property`, + }, + }, +} as const; + +export const $ModelWithNullableString = { + description: `This is a model with one string property`, + properties: { + nullableProp1: { + type: 'string', + description: `This is a simple string property`, + isNullable: true, + }, + nullableRequiredProp1: { + type: 'string', + description: `This is a simple string property`, + isRequired: true, + isNullable: true, + }, + nullableProp2: { + type: 'string', + description: `This is a simple string property`, + isNullable: true, + }, + nullableRequiredProp2: { + type: 'string', + description: `This is a simple string property`, + isRequired: true, + isNullable: true, + }, + }, +} as const; + +export const $ModelWithEnum = { + description: `This is a model with one enum`, + properties: { + test: { + type: 'Enum', + }, + statusCode: { + type: 'Enum', + }, + bool: { + type: 'boolean', + description: `Simple boolean enum`, + }, + }, +} as const; + +export const $ModelWithEnumWithHyphen = { + description: `This is a model with one enum with escaped name`, + properties: { + 'foo-bar-baz-qux': { + type: 'Enum', + }, + }, +} as const; + +export const $ModelWithEnumFromDescription = { + description: `This is a model with one enum`, + properties: { + test: { + type: 'number', + description: `Success=1,Warning=2,Error=3`, + }, + }, +} as const; + +export const $ModelWithNestedEnums = { + description: `This is a model with nested enums`, + properties: { + dictionaryWithEnum: { + type: 'dictionary', + contains: { + type: 'Enum', + }, + }, + dictionaryWithEnumFromDescription: { + type: 'dictionary', + contains: { + type: 'number', + description: `Success=1,Warning=2,Error=3`, + }, + }, + arrayWithEnum: { + type: 'array', + contains: { + type: 'Enum', + }, + }, + arrayWithDescription: { + type: 'array', + contains: { + type: 'number', + description: `Success=1,Warning=2,Error=3`, + }, + }, + }, +} as const; + +export const $ModelWithReference = { + description: `This is a model with one property containing a reference`, + properties: { + prop: { + type: 'ModelWithProperties', + }, + }, +} as const; + +export const $ModelWithArrayReadOnlyAndWriteOnly = { + description: `This is a model with one property containing an array`, + properties: { + prop: { + type: 'array', + contains: { + type: 'ModelWithReadOnlyAndWriteOnly', + }, + }, + propWithFile: { + type: 'array', + contains: { + type: 'binary', + }, + }, + propWithNumber: { + type: 'array', + contains: { + type: 'number', + }, + }, + }, +} as const; + +export const $ModelWithArray = { + description: `This is a model with one property containing an array`, + properties: { + prop: { + type: 'array', + contains: { + type: 'ModelWithString', + }, + }, + propWithFile: { + type: 'array', + contains: { + type: 'binary', + }, + }, + propWithNumber: { + type: 'array', + contains: { + type: 'number', + }, + }, + }, +} as const; + +export const $ModelWithDictionary = { + description: `This is a model with one property containing a dictionary`, + properties: { + prop: { + type: 'dictionary', + contains: { + type: 'string', + }, + }, + }, +} as const; + +export const $DeprecatedModel = { + description: `This is a deprecated model with a deprecated property`, + properties: { + prop: { + type: 'string', + description: `This is a deprecated property`, + }, + }, +} as const; + +export const $ModelWithCircularReference = { + description: `This is a model with one property containing a circular reference`, + properties: { + prop: { + type: 'ModelWithCircularReference', + }, + }, +} as const; + +export const $CompositionWithOneOf = { + description: `This is a model with one property with a 'one of' relationship`, + properties: { + propA: { + type: 'one-of', + contains: [ + { + type: 'ModelWithString', + }, + { + type: 'ModelWithEnum', + }, + { + type: 'ModelWithArray', + }, + { + type: 'ModelWithDictionary', + }, + ], + }, + }, +} as const; + +export const $CompositionWithOneOfAnonymous = { + description: `This is a model with one property with a 'one of' relationship where the options are not $ref`, + properties: { + propA: { + type: 'one-of', + contains: [ + { + description: `Anonymous object type`, + properties: { + propA: { + type: 'string', + }, + }, + }, + { + type: 'string', + description: `Anonymous string type`, + }, + { + type: 'number', + description: `Anonymous integer type`, + }, + ], + }, + }, +} as const; + +export const $ModelCircle = { + description: `Circle`, + properties: { + kind: { + type: 'string', + isRequired: true, + }, + radius: { + type: 'number', + }, + }, +} as const; + +export const $ModelSquare = { + description: `Square`, + properties: { + kind: { + type: 'string', + isRequired: true, + }, + sideLength: { + type: 'number', + }, + }, +} as const; + +export const $CompositionWithOneOfDiscriminator = { + type: 'one-of', + description: `This is a model with one property with a 'one of' relationship where the options are not $ref`, + contains: [ + { + type: 'ModelCircle', + }, + { + type: 'ModelSquare', + }, + ], +} as const; + +export const $CompositionWithAnyOf = { + description: `This is a model with one property with a 'any of' relationship`, + properties: { + propA: { + type: 'any-of', + contains: [ + { + type: 'ModelWithString', + }, + { + type: 'ModelWithEnum', + }, + { + type: 'ModelWithArray', + }, + { + type: 'ModelWithDictionary', + }, + ], + }, + }, +} as const; + +export const $CompositionWithAnyOfAnonymous = { + description: `This is a model with one property with a 'any of' relationship where the options are not $ref`, + properties: { + propA: { + type: 'any-of', + contains: [ + { + description: `Anonymous object type`, + properties: { + propA: { + type: 'string', + }, + }, + }, + { + type: 'string', + description: `Anonymous string type`, + }, + { + type: 'number', + description: `Anonymous integer type`, + }, + ], + }, + }, +} as const; + +export const $CompositionWithNestedAnyAndTypeNull = { + description: `This is a model with nested 'any of' property with a type null`, + properties: { + propA: { + type: 'any-of', + contains: [ + { + type: 'array', + contains: { + type: 'any-of', + contains: [ + { + type: 'ModelWithDictionary', + }, + { + type: 'null', + }, + ], + }, + }, + { + type: 'array', + contains: { + type: 'any-of', + contains: [ + { + type: 'ModelWithArray', + }, + { + type: 'null', + }, + ], + }, + }, + ], + }, + }, +} as const; + +export const $Enum1 = { + type: 'Enum', +} as const; + +export const $ConstValue = { + type: '"ConstValue"', +} as const; + +export const $CompositionWithNestedAnyOfAndNull = { + description: `This is a model with one property with a 'any of' relationship where the options are not $ref`, + properties: { + propA: { + type: 'any-of', + contains: [ + { + type: 'array', + contains: { + type: 'any-of', + contains: [ + { + type: 'Enum1', + }, + { + type: 'ConstValue', + }, + ], + }, + }, + { + type: 'null', + }, + ], + }, + }, +} as const; + +export const $CompositionWithOneOfAndNullable = { + description: `This is a model with one property with a 'one of' relationship`, + properties: { + propA: { + type: 'one-of', + contains: [ + { + properties: { + boolean: { + type: 'boolean', + }, + }, + }, + { + type: 'ModelWithEnum', + }, + { + type: 'ModelWithArray', + }, + { + type: 'ModelWithDictionary', + }, + ], + isNullable: true, + }, + }, +} as const; + +export const $CompositionWithOneOfAndSimpleDictionary = { + description: `This is a model that contains a simple dictionary within composition`, + properties: { + propA: { + type: 'one-of', + contains: [ + { + type: 'boolean', + }, + { + type: 'dictionary', + contains: { + type: 'number', + }, + }, + ], + }, + }, +} as const; + +export const $CompositionWithOneOfAndSimpleArrayDictionary = { + description: `This is a model that contains a dictionary of simple arrays within composition`, + properties: { + propA: { + type: 'one-of', + contains: [ + { + type: 'boolean', + }, + { + type: 'dictionary', + contains: { + type: 'array', + contains: { + type: 'boolean', + }, + }, + }, + ], + }, + }, +} as const; + +export const $CompositionWithOneOfAndComplexArrayDictionary = { + description: `This is a model that contains a dictionary of complex arrays (composited) within composition`, + properties: { + propA: { + type: 'one-of', + contains: [ + { + type: 'boolean', + }, + { + type: 'dictionary', + contains: { + type: 'array', + contains: { + type: 'one-of', + contains: [ + { + type: 'number', + }, + { + type: 'string', + }, + ], + }, + }, + }, + ], + }, + }, +} as const; + +export const $CompositionWithAllOfAndNullable = { + description: `This is a model with one property with a 'all of' relationship`, + properties: { + propA: { + type: 'all-of', + contains: [ + { + properties: { + boolean: { + type: 'boolean', + }, + }, + }, + { + type: 'ModelWithEnum', + }, + { + type: 'ModelWithArray', + }, + { + type: 'ModelWithDictionary', + }, + ], + isNullable: true, + }, + }, +} as const; + +export const $CompositionWithAnyOfAndNullable = { + description: `This is a model with one property with a 'any of' relationship`, + properties: { + propA: { + type: 'any-of', + contains: [ + { + properties: { + boolean: { + type: 'boolean', + }, + }, + }, + { + type: 'ModelWithEnum', + }, + { + type: 'ModelWithArray', + }, + { + type: 'ModelWithDictionary', + }, + ], + isNullable: true, + }, + }, +} as const; + +export const $CompositionBaseModel = { + description: `This is a base model with two simple optional properties`, + properties: { + firstName: { + type: 'string', + }, + lastname: { + type: 'string', + }, + }, +} as const; + +export const $CompositionExtendedModel = { + type: 'all-of', + description: `This is a model that extends the base model`, + contains: [ + { + type: 'CompositionBaseModel', + }, + { + properties: { + firstName: { + type: 'string', + isRequired: true, + }, + lastname: { + type: 'string', + isRequired: true, + }, + age: { + type: 'number', + isRequired: true, + }, + }, + }, + ], +} as const; + +export const $ModelWithProperties = { + description: `This is a model with one nested property`, + properties: { + required: { + type: 'string', + isRequired: true, + }, + requiredAndReadOnly: { + type: 'string', + isReadOnly: true, + isRequired: true, + }, + requiredAndNullable: { + type: 'string', + isRequired: true, + isNullable: true, + }, + string: { + type: 'string', + }, + number: { + type: 'number', + }, + boolean: { + type: 'boolean', + }, + reference: { + type: 'ModelWithString', + }, + 'property with space': { + type: 'string', + }, + default: { + type: 'string', + }, + try: { + type: 'string', + }, + '@namespace.string': { + type: 'string', + isReadOnly: true, + }, + '@namespace.integer': { + type: 'number', + isReadOnly: true, + }, + }, +} as const; + +export const $ModelWithNestedProperties = { + description: `This is a model with one nested property`, + properties: { + first: { + properties: { + second: { + properties: { + third: { + type: 'string', + isReadOnly: true, + isRequired: true, + isNullable: true, + }, + }, + isReadOnly: true, + isRequired: true, + isNullable: true, + }, + }, + isReadOnly: true, + isRequired: true, + isNullable: true, + }, + }, +} as const; + +export const $ModelWithDuplicateProperties = { + description: `This is a model with duplicated properties`, + properties: { + prop: { + type: 'ModelWithString', + }, + }, +} as const; + +export const $ModelWithOrderedProperties = { + description: `This is a model with ordered properties`, + properties: { + zebra: { + type: 'string', + }, + apple: { + type: 'string', + }, + hawaii: { + type: 'string', + }, + }, +} as const; + +export const $ModelWithDuplicateImports = { + description: `This is a model with duplicated imports`, + properties: { + propA: { + type: 'ModelWithString', + }, + propB: { + type: 'ModelWithString', + }, + propC: { + type: 'ModelWithString', + }, + }, +} as const; + +export const $ModelThatExtends = { + type: 'all-of', + description: `This is a model that extends another model`, + contains: [ + { + type: 'ModelWithString', + }, + { + properties: { + propExtendsA: { + type: 'string', + }, + propExtendsB: { + type: 'ModelWithString', + }, + }, + }, + ], +} as const; + +export const $ModelThatExtendsExtends = { + type: 'all-of', + description: `This is a model that extends another model`, + contains: [ + { + type: 'ModelWithString', + }, + { + type: 'ModelThatExtends', + }, + { + properties: { + propExtendsC: { + type: 'string', + }, + propExtendsD: { + type: 'ModelWithString', + }, + }, + }, + ], +} as const; + +export const $ModelWithPattern = { + description: `This is a model that contains a some patterns`, + properties: { + key: { + type: 'string', + isRequired: true, + maxLength: 64, + pattern: '^[a-zA-Z0-9_]*$', + }, + name: { + type: 'string', + isRequired: true, + maxLength: 255, + }, + enabled: { + type: 'boolean', + isReadOnly: true, + }, + modified: { + type: 'string', + isReadOnly: true, + format: 'date-time', + }, + id: { + type: 'string', + pattern: '^\\d{2}-\\d{3}-\\d{4}$', + }, + text: { + type: 'string', + pattern: '^\\w+$', + }, + patternWithSingleQuotes: { + type: 'string', + pattern: "^[a-zA-Z0-9']*$", + }, + patternWithNewline: { + type: 'string', + pattern: 'aaa\nbbb', + }, + patternWithBacktick: { + type: 'string', + pattern: 'aaa`bbb', + }, + }, +} as const; + +export const $File = { + properties: { + id: { + type: 'string', + isReadOnly: true, + minLength: 1, + }, + updated_at: { + type: 'string', + isReadOnly: true, + format: 'date-time', + }, + created_at: { + type: 'string', + isReadOnly: true, + format: 'date-time', + }, + mime: { + type: 'string', + isRequired: true, + maxLength: 24, + minLength: 1, + }, + file: { + type: 'string', + isReadOnly: true, + format: 'uri', + }, + }, +} as const; + +export const $_default = { + properties: { + name: { + type: 'string', + }, + }, +} as const; + +export const $Pageable = { + properties: { + page: { + type: 'number', + format: 'int32', + }, + size: { + type: 'number', + format: 'int32', + minimum: 1, + }, + sort: { + type: 'array', + contains: { + type: 'string', + }, + }, + }, +} as const; + +export const $FreeFormObjectWithoutAdditionalProperties = { + type: 'dictionary', + contains: { + properties: {}, + }, +} as const; + +export const $FreeFormObjectWithAdditionalPropertiesEqTrue = { + type: 'dictionary', + contains: { + properties: {}, + }, +} as const; + +export const $FreeFormObjectWithAdditionalPropertiesEqEmptyObject = { + type: 'dictionary', + contains: { + properties: {}, + }, +} as const; + +export const $ModelWithConst = { + properties: { + String: { + type: '"String"', + }, + number: { + type: '0', + }, + null: { + type: 'null', + }, + withType: { + type: '"Some string"', + }, + }, +} as const; + +export const $ModelWithAdditionalPropertiesEqTrue = { + description: `This is a model with one property and additionalProperties: true`, + properties: { + prop: { + type: 'string', + description: `This is a simple string property`, + }, + }, +} as const; + +export const $NestedAnyOfArraysNullable = { + properties: { + nullableArray: { + type: 'any-of', + contains: [ + { + type: 'array', + contains: { + type: 'any-of', + contains: [ + { + type: 'string', + }, + { + type: 'boolean', + }, + ], + }, + }, + { + type: 'null', + }, + ], + }, + }, +} as const; + +export const $CompositionWithOneOfAndProperties = { + type: 'one-of', + contains: [ + { + properties: { + foo: { + type: 'SimpleParameter', + isRequired: true, + }, + baz: { + type: 'number', + isRequired: true, + isNullable: true, + format: 'uint16', + }, + qux: { + type: 'number', + isRequired: true, + format: 'uint8', + }, + }, + }, + { + properties: { + bar: { + type: 'NonAsciiStringæøåÆØÅöôêÊ字符串', + isRequired: true, + }, + baz: { + type: 'number', + isRequired: true, + isNullable: true, + format: 'uint16', + }, + qux: { + type: 'number', + isRequired: true, + format: 'uint8', + }, + }, + }, + ], +} as const; + +export const $NullableObject = { + description: `An object that can be null`, + properties: { + foo: { + type: 'string', + }, + }, + isNullable: true, +} as const; + +export const $ModelWithNullableObject = { + properties: { + data: { + type: 'NullableObject', + }, + }, +} as const; + +export const $ModelWithOneOfEnum = { + type: 'one-of', + contains: [ + { + properties: { + foo: { + type: 'Enum', + isRequired: true, + }, + }, + }, + { + properties: { + foo: { + type: 'Enum', + isRequired: true, + }, + }, + }, + { + properties: { + foo: { + type: 'Enum', + isRequired: true, + }, + }, + }, + { + properties: { + content: { + type: 'string', + isRequired: true, + format: 'date-time', + }, + foo: { + type: 'Enum', + isRequired: true, + }, + }, + }, + { + properties: { + content: { + type: 'array', + contains: { + type: 'any-of', + contains: [ + { + type: 'string', + format: 'date-time', + }, + { + type: 'string', + }, + ], + }, + isRequired: true, + }, + foo: { + type: 'Enum', + isRequired: true, + }, + }, + }, + ], +} as const; + +export const $ModelWithNestedArrayEnumsDataFoo = { + type: 'Enum', +} as const; + +export const $ModelWithNestedArrayEnumsDataBar = { + type: 'Enum', +} as const; + +export const $ModelWithNestedArrayEnumsData = { + properties: { + foo: { + type: 'array', + contains: { + type: 'ModelWithNestedArrayEnumsDataFoo', + }, + }, + bar: { + type: 'array', + contains: { + type: 'ModelWithNestedArrayEnumsDataBar', + }, + }, + }, +} as const; + +export const $ModelWithNestedArrayEnums = { + properties: { + array_strings: { + type: 'array', + contains: { + type: 'string', + }, + }, + data: { + type: 'all-of', + contains: [ + { + type: 'ModelWithNestedArrayEnumsData', + }, + ], + }, + }, +} as const; + +export const $ModelWithNestedCompositionEnums = { + properties: { + foo: { + type: 'all-of', + contains: [ + { + type: 'ModelWithNestedArrayEnumsDataFoo', + }, + ], + }, + }, +} as const; + +export const $ModelWithReadOnlyAndWriteOnly = { + properties: { + foo: { + type: 'string', + isRequired: true, + }, + bar: { + type: 'string', + isReadOnly: true, + isRequired: true, + }, + baz: { + type: 'string', + isRequired: true, + }, + }, +} as const; + +export const $SimpleParameter = { + type: 'string', + description: `This is a reusable parameter`, +} as const; diff --git a/test/__snapshots__/v3_angular/services/CollectionFormatService.ts.snap b/test/__snapshots__/v3_angular/services/CollectionFormatService.ts.snap new file mode 100644 index 000000000..5f6053f35 --- /dev/null +++ b/test/__snapshots__/v3_angular/services/CollectionFormatService.ts.snap @@ -0,0 +1,55 @@ +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'; + +export type TDataCollectionFormat = { + /** + * This is an array parameter that is sent as csv format (comma-separated values) + */ + parameterArrayCsv: Array | null; + /** + * This is an array parameter that is sent as multi format (multiple parameter instances) + */ + parameterArrayMulti: Array | null; + /** + * This is an array parameter that is sent as pipes format (pipe-separated values) + */ + parameterArrayPipes: Array | null; + /** + * This is an array parameter that is sent as ssv format (space-separated values) + */ + parameterArraySsv: Array | null; + /** + * This is an array parameter that is sent as tsv format (tab-separated values) + */ + parameterArrayTsv: Array | null; +}; + +@Injectable({ + providedIn: 'root', +}) +export class CollectionFormatService { + constructor(public readonly http: HttpClient) {} + + /** + * @throws ApiError + */ + public collectionFormat(data: TDataCollectionFormat): Observable { + const { parameterArrayCsv, parameterArrayMulti, parameterArrayPipes, parameterArraySsv, parameterArrayTsv } = + data; + return __request(OpenAPI, this.http, { + method: 'GET', + url: '/api/v{api-version}/collectionFormat', + query: { + parameterArrayCSV: parameterArrayCsv, + parameterArraySSV: parameterArraySsv, + parameterArrayTSV: parameterArrayTsv, + parameterArrayPipes, + parameterArrayMulti, + }, + }); + } +} diff --git a/test/__snapshots__/v3_angular/services/ComplexService.ts.snap b/test/__snapshots__/v3_angular/services/ComplexService.ts.snap new file mode 100644 index 000000000..ef303c6af --- /dev/null +++ b/test/__snapshots__/v3_angular/services/ComplexService.ts.snap @@ -0,0 +1,86 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import type { Observable } from 'rxjs'; +import type { ModelWithArray } from '../models'; +import type { ModelWithDictionary } from '../models'; +import type { ModelWithEnum } from '../models'; +import type { ModelWithString } from '../models'; +import { OpenAPI } from '../core/OpenAPI'; +import { request as __request } from '../core/request'; + +export type TDataComplexTypes = { + /** + * Parameter containing object + */ + parameterObject: { + first?: { + second?: { + third?: string; + }; + }; + }; + /** + * Parameter containing reference + */ + parameterReference: ModelWithString; +}; +export type TDataComplexParams = { + id: number; + requestBody?: { + readonly key: string | null; + name: string | null; + enabled?: boolean; + readonly type: 'Monkey' | 'Horse' | 'Bird'; + listOfModels?: Array | null; + listOfStrings?: Array | null; + parameters: ModelWithString | ModelWithEnum | ModelWithArray | ModelWithDictionary; + readonly user?: { + readonly id?: number; + readonly name?: string | null; + }; + }; +}; + +@Injectable({ + providedIn: 'root', +}) +export class ComplexService { + constructor(public readonly http: HttpClient) {} + + /** + * @returns ModelWithString Successful response + * @throws ApiError + */ + public complexTypes(data: TDataComplexTypes): Observable> { + const { parameterObject, parameterReference } = data; + return __request(OpenAPI, this.http, { + method: 'GET', + url: '/api/v{api-version}/complex', + query: { + parameterObject, + parameterReference, + }, + errors: { + 400: `400 server error`, + 500: `500 server error`, + }, + }); + } + + /** + * @returns ModelWithString Success + * @throws ApiError + */ + public complexParams(data: TDataComplexParams): Observable { + const { id, requestBody } = data; + return __request(OpenAPI, this.http, { + method: 'PUT', + url: '/api/v{api-version}/complex/{id}', + path: { + id, + }, + body: requestBody, + mediaType: 'application/json-patch+json', + }); + } +} diff --git a/test/__snapshots__/v3_angular/services/DefaultService.ts.snap b/test/__snapshots__/v3_angular/services/DefaultService.ts.snap new file mode 100644 index 000000000..ae4bcdd0d --- /dev/null +++ b/test/__snapshots__/v3_angular/services/DefaultService.ts.snap @@ -0,0 +1,42 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import type { Observable } from 'rxjs'; +import type { ModelWithArrayReadOnlyAndWriteOnly } from '../models'; +import type { ModelWithReadOnlyAndWriteOnly } from '../models'; +import { OpenAPI } from '../core/OpenAPI'; +import { request as __request } from '../core/request'; + +export type TDataPostServiceWithEmptyTag = { + requestBody: ModelWithReadOnlyAndWriteOnly | ModelWithArrayReadOnlyAndWriteOnly; +}; + +@Injectable({ + providedIn: 'root', +}) +export class DefaultService { + constructor(public readonly http: HttpClient) {} + + /** + * @throws ApiError + */ + public serviceWithEmptyTag(): Observable { + return __request(OpenAPI, this.http, { + method: 'GET', + url: '/api/v{api-version}/no-tag', + }); + } + + /** + * @returns ModelWithReadOnlyAndWriteOnly + * @throws ApiError + */ + public postServiceWithEmptyTag(data: TDataPostServiceWithEmptyTag): Observable { + const { requestBody } = data; + return __request(OpenAPI, this.http, { + method: 'POST', + url: '/api/v{api-version}/no-tag', + body: requestBody, + mediaType: 'application/json', + }); + } +} diff --git a/test/__snapshots__/v3_angular/services/DefaultsService.ts.snap b/test/__snapshots__/v3_angular/services/DefaultsService.ts.snap new file mode 100644 index 000000000..7d2dab76c --- /dev/null +++ b/test/__snapshots__/v3_angular/services/DefaultsService.ts.snap @@ -0,0 +1,174 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import type { Observable } from 'rxjs'; +import type { ModelWithString } from '../models'; +import { OpenAPI } from '../core/OpenAPI'; +import { request as __request } from '../core/request'; + +export type TDataCallWithDefaultParameters = { + /** + * 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 TDataCallWithDefaultOptionalParameters = { + /** + * 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 TDataCallToTestOrderOfParams = { + /** + * 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; +}; + +@Injectable({ + providedIn: 'root', +}) +export class DefaultsService { + constructor(public readonly http: HttpClient) {} + + /** + * @throws ApiError + */ + public callWithDefaultParameters(data: TDataCallWithDefaultParameters = {}): Observable { + const { + parameterBoolean = true, + parameterEnum = 'Success', + parameterModel = { + prop: 'Hello World!', + }, + parameterNumber = 123, + parameterString = 'Hello World!', + } = data; + return __request(OpenAPI, this.http, { + method: 'GET', + url: '/api/v{api-version}/defaults', + query: { + parameterString, + parameterNumber, + parameterBoolean, + parameterEnum, + parameterModel, + }, + }); + } + + /** + * @throws ApiError + */ + public callWithDefaultOptionalParameters(data: TDataCallWithDefaultOptionalParameters = {}): Observable { + const { + parameterBoolean = true, + parameterEnum = 'Success', + parameterModel = { + prop: 'Hello World!', + }, + parameterNumber = 123, + parameterString = 'Hello World!', + } = data; + return __request(OpenAPI, this.http, { + method: 'POST', + url: '/api/v{api-version}/defaults', + query: { + parameterString, + parameterNumber, + parameterBoolean, + parameterEnum, + parameterModel, + }, + }); + } + + /** + * @throws ApiError + */ + public callToTestOrderOfParams(data: TDataCallToTestOrderOfParams): Observable { + const { + parameterOptionalStringWithDefault = 'Hello World!', + parameterOptionalStringWithEmptyDefault = '', + parameterOptionalStringWithNoDefault, + parameterStringNullableWithDefault = null, + parameterStringNullableWithNoDefault, + parameterStringWithDefault = 'Hello World!', + parameterStringWithEmptyDefault = '', + parameterStringWithNoDefault, + } = data; + return __request(OpenAPI, this.http, { + method: 'PUT', + url: '/api/v{api-version}/defaults', + query: { + parameterOptionalStringWithDefault, + parameterOptionalStringWithEmptyDefault, + parameterOptionalStringWithNoDefault, + parameterStringWithDefault, + parameterStringWithEmptyDefault, + parameterStringWithNoDefault, + parameterStringNullableWithNoDefault, + parameterStringNullableWithDefault, + }, + }); + } +} diff --git a/test/__snapshots__/v3_angular/services/DeprecatedService.ts.snap b/test/__snapshots__/v3_angular/services/DeprecatedService.ts.snap new file mode 100644 index 000000000..ea44320f7 --- /dev/null +++ b/test/__snapshots__/v3_angular/services/DeprecatedService.ts.snap @@ -0,0 +1,35 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import type { Observable } from 'rxjs'; +import type { DeprecatedModel } from '../models'; +import { OpenAPI } from '../core/OpenAPI'; +import { request as __request } from '../core/request'; + +export type TDataDeprecatedCall = { + /** + * This parameter is deprecated + */ + parameter: DeprecatedModel | null; +}; + +@Injectable({ + providedIn: 'root', +}) +export class DeprecatedService { + constructor(public readonly http: HttpClient) {} + + /** + * @deprecated + * @throws ApiError + */ + public deprecatedCall(data: TDataDeprecatedCall): Observable { + const { parameter } = data; + return __request(OpenAPI, this.http, { + method: 'POST', + url: '/api/v{api-version}/parameters/deprecated', + headers: { + parameter, + }, + }); + } +} diff --git a/test/__snapshots__/v3_angular/services/DescriptionsService.ts.snap b/test/__snapshots__/v3_angular/services/DescriptionsService.ts.snap new file mode 100644 index 000000000..12aae77b9 --- /dev/null +++ b/test/__snapshots__/v3_angular/services/DescriptionsService.ts.snap @@ -0,0 +1,69 @@ +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'; + +export type TDataCallWithDescriptions = { + /** + * 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; +}; + +@Injectable({ + providedIn: 'root', +}) +export class DescriptionsService { + constructor(public readonly http: HttpClient) {} + + /** + * @throws ApiError + */ + public callWithDescriptions(data: TDataCallWithDescriptions = {}): Observable { + const { + parameterWithBackticks, + parameterWithBreaks, + parameterWithExpressionPlaceholders, + parameterWithQuotes, + parameterWithReservedCharacters, + parameterWithSlashes, + } = data; + return __request(OpenAPI, this.http, { + method: 'POST', + url: '/api/v{api-version}/descriptions/', + query: { + parameterWithBreaks, + parameterWithBackticks, + parameterWithSlashes, + parameterWithExpressionPlaceholders, + parameterWithQuotes, + parameterWithReservedCharacters, + }, + }); + } +} diff --git a/test/__snapshots__/v3_angular/services/DuplicateService.ts.snap b/test/__snapshots__/v3_angular/services/DuplicateService.ts.snap new file mode 100644 index 000000000..55b0c72e8 --- /dev/null +++ b/test/__snapshots__/v3_angular/services/DuplicateService.ts.snap @@ -0,0 +1,53 @@ +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'; + +@Injectable({ + providedIn: 'root', +}) +export class DuplicateService { + constructor(public readonly http: HttpClient) {} + + /** + * @throws ApiError + */ + public duplicateName(): Observable { + return __request(OpenAPI, this.http, { + method: 'GET', + url: '/api/v{api-version}/duplicate', + }); + } + + /** + * @throws ApiError + */ + public duplicateName1(): Observable { + return __request(OpenAPI, this.http, { + method: 'POST', + url: '/api/v{api-version}/duplicate', + }); + } + + /** + * @throws ApiError + */ + public duplicateName2(): Observable { + return __request(OpenAPI, this.http, { + method: 'PUT', + url: '/api/v{api-version}/duplicate', + }); + } + + /** + * @throws ApiError + */ + public duplicateName3(): Observable { + return __request(OpenAPI, this.http, { + method: 'DELETE', + url: '/api/v{api-version}/duplicate', + }); + } +} diff --git a/test/__snapshots__/v3_angular/services/ErrorService.ts.snap b/test/__snapshots__/v3_angular/services/ErrorService.ts.snap new file mode 100644 index 000000000..de9bf6143 --- /dev/null +++ b/test/__snapshots__/v3_angular/services/ErrorService.ts.snap @@ -0,0 +1,41 @@ +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'; + +export type TDataTestErrorCode = { + /** + * Status code to return + */ + status: number; +}; + +@Injectable({ + providedIn: 'root', +}) +export class ErrorService { + constructor(public readonly http: HttpClient) {} + + /** + * @returns any Custom message: Successful response + * @throws ApiError + */ + public testErrorCode(data: TDataTestErrorCode): Observable { + const { status } = data; + return __request(OpenAPI, this.http, { + method: 'POST', + url: '/api/v{api-version}/error', + query: { + status, + }, + errors: { + 500: `Custom message: Internal Server Error`, + 501: `Custom message: Not Implemented`, + 502: `Custom message: Bad Gateway`, + 503: `Custom message: Service Unavailable`, + }, + }); + } +} diff --git a/test/__snapshots__/v3_angular/services/FileResponseService.ts.snap b/test/__snapshots__/v3_angular/services/FileResponseService.ts.snap new file mode 100644 index 000000000..c068d26f1 --- /dev/null +++ b/test/__snapshots__/v3_angular/services/FileResponseService.ts.snap @@ -0,0 +1,32 @@ +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'; + +export type TDataFileResponse = { + id: string; +}; + +@Injectable({ + providedIn: 'root', +}) +export class FileResponseService { + constructor(public readonly http: HttpClient) {} + + /** + * @returns binary Success + * @throws ApiError + */ + public fileResponse(data: TDataFileResponse): Observable { + const { id } = data; + return __request(OpenAPI, this.http, { + method: 'GET', + url: '/api/v{api-version}/file/{id}', + path: { + id, + }, + }); + } +} diff --git a/test/__snapshots__/v3_angular/services/FormDataService.ts.snap b/test/__snapshots__/v3_angular/services/FormDataService.ts.snap new file mode 100644 index 000000000..93fef2ab6 --- /dev/null +++ b/test/__snapshots__/v3_angular/services/FormDataService.ts.snap @@ -0,0 +1,40 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import type { Observable } from 'rxjs'; +import type { ModelWithString } from '../models'; +import { OpenAPI } from '../core/OpenAPI'; +import { request as __request } from '../core/request'; + +export type TDataPostApiFormData = { + /** + * A reusable request body + */ + formData?: ModelWithString; + /** + * This is a reusable parameter + */ + parameter?: string; +}; + +@Injectable({ + providedIn: 'root', +}) +export class FormDataService { + constructor(public readonly http: HttpClient) {} + + /** + * @throws ApiError + */ + public postApiFormData(data: TDataPostApiFormData = {}): Observable { + const { formData, parameter } = data; + return __request(OpenAPI, this.http, { + method: 'POST', + url: '/api/v{api-version}/formData/', + query: { + parameter, + }, + formData: formData, + mediaType: 'multipart/form-data', + }); + } +} diff --git a/test/__snapshots__/v3_angular/services/HeaderService.ts.snap b/test/__snapshots__/v3_angular/services/HeaderService.ts.snap new file mode 100644 index 000000000..ce02d3f58 --- /dev/null +++ b/test/__snapshots__/v3_angular/services/HeaderService.ts.snap @@ -0,0 +1,29 @@ +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'; + +@Injectable({ + providedIn: 'root', +}) +export class HeaderService { + constructor(public readonly http: HttpClient) {} + + /** + * @returns string Successful response + * @throws ApiError + */ + public callWithResultFromHeader(): 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`, + }, + }); + } +} diff --git a/test/__snapshots__/v3_angular/services/MultipartService.ts.snap b/test/__snapshots__/v3_angular/services/MultipartService.ts.snap new file mode 100644 index 000000000..ec18241ff --- /dev/null +++ b/test/__snapshots__/v3_angular/services/MultipartService.ts.snap @@ -0,0 +1,50 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import type { Observable } from 'rxjs'; +import type { ModelWithString } from '../models'; +import { OpenAPI } from '../core/OpenAPI'; +import { request as __request } from '../core/request'; + +export type TDataMultipartRequest = { + formData?: { + content?: Blob; + data?: ModelWithString | null; + }; +}; + +@Injectable({ + providedIn: 'root', +}) +export class MultipartService { + constructor(public readonly http: HttpClient) {} + + /** + * @throws ApiError + */ + public multipartRequest(data: TDataMultipartRequest = {}): Observable { + const { formData } = data; + return __request(OpenAPI, this.http, { + method: 'POST', + url: '/api/v{api-version}/multipart', + formData: formData, + mediaType: 'multipart/form-data', + }); + } + + /** + * @returns any OK + * @throws ApiError + */ + public multipartResponse(): Observable<{ + file?: Blob; + metadata?: { + foo?: string; + bar?: string; + }; + }> { + return __request(OpenAPI, this.http, { + method: 'GET', + url: '/api/v{api-version}/multipart', + }); + } +} diff --git a/test/__snapshots__/v3_angular/services/MultipleTags1Service.ts.snap b/test/__snapshots__/v3_angular/services/MultipleTags1Service.ts.snap new file mode 100644 index 000000000..7480c3955 --- /dev/null +++ b/test/__snapshots__/v3_angular/services/MultipleTags1Service.ts.snap @@ -0,0 +1,35 @@ +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'; + +@Injectable({ + providedIn: 'root', +}) +export class MultipleTags1Service { + constructor(public readonly http: HttpClient) {} + + /** + * @returns void Success + * @throws ApiError + */ + public dummyA(): Observable { + return __request(OpenAPI, this.http, { + method: 'GET', + url: '/api/v{api-version}/multiple-tags/a', + }); + } + + /** + * @returns void Success + * @throws ApiError + */ + public dummyB(): Observable { + return __request(OpenAPI, this.http, { + method: 'GET', + url: '/api/v{api-version}/multiple-tags/b', + }); + } +} diff --git a/test/__snapshots__/v3_angular/services/MultipleTags2Service.ts.snap b/test/__snapshots__/v3_angular/services/MultipleTags2Service.ts.snap new file mode 100644 index 000000000..187c68fe6 --- /dev/null +++ b/test/__snapshots__/v3_angular/services/MultipleTags2Service.ts.snap @@ -0,0 +1,35 @@ +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'; + +@Injectable({ + providedIn: 'root', +}) +export class MultipleTags2Service { + constructor(public readonly http: HttpClient) {} + + /** + * @returns void Success + * @throws ApiError + */ + public dummyA(): Observable { + return __request(OpenAPI, this.http, { + method: 'GET', + url: '/api/v{api-version}/multiple-tags/a', + }); + } + + /** + * @returns void Success + * @throws ApiError + */ + public dummyB(): Observable { + return __request(OpenAPI, this.http, { + method: 'GET', + url: '/api/v{api-version}/multiple-tags/b', + }); + } +} diff --git a/test/__snapshots__/v3_angular/services/MultipleTags3Service.ts.snap b/test/__snapshots__/v3_angular/services/MultipleTags3Service.ts.snap new file mode 100644 index 000000000..4361ff7ee --- /dev/null +++ b/test/__snapshots__/v3_angular/services/MultipleTags3Service.ts.snap @@ -0,0 +1,24 @@ +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'; + +@Injectable({ + providedIn: 'root', +}) +export class MultipleTags3Service { + constructor(public readonly http: HttpClient) {} + + /** + * @returns void Success + * @throws ApiError + */ + public dummyB(): Observable { + return __request(OpenAPI, this.http, { + method: 'GET', + url: '/api/v{api-version}/multiple-tags/b', + }); + } +} diff --git a/test/__snapshots__/v3_angular/services/NoContentService.ts.snap b/test/__snapshots__/v3_angular/services/NoContentService.ts.snap new file mode 100644 index 000000000..cd0cc4cd3 --- /dev/null +++ b/test/__snapshots__/v3_angular/services/NoContentService.ts.snap @@ -0,0 +1,36 @@ +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'; + +@Injectable({ + providedIn: 'root', +}) +export class NoContentService { + constructor(public readonly http: HttpClient) {} + + /** + * @returns void Success + * @throws ApiError + */ + public callWithNoContentResponse(): 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 + */ + public callWithResponseAndNoContentResponse(): Observable { + return __request(OpenAPI, this.http, { + method: 'GET', + url: '/api/v{api-version}/multiple-tags/response-and-no-content', + }); + } +} diff --git "a/test/__snapshots__/v3_angular/services/NonAscii\303\206\303\270\303\245\303\206\303\270\303\205\303\266\303\264\303\252\303\212Service.ts.snap" "b/test/__snapshots__/v3_angular/services/NonAscii\303\206\303\270\303\245\303\206\303\270\303\205\303\266\303\264\303\252\303\212Service.ts.snap" new file mode 100644 index 000000000..2f931c93e --- /dev/null +++ "b/test/__snapshots__/v3_angular/services/NonAscii\303\206\303\270\303\245\303\206\303\270\303\205\303\266\303\264\303\252\303\212Service.ts.snap" @@ -0,0 +1,37 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import type { Observable } from 'rxjs'; +import type { NonAsciiStringæøåÆØÅöôêÊ字符串 } from '../models'; +import { OpenAPI } from '../core/OpenAPI'; +import { request as __request } from '../core/request'; + +export type TDataNonAsciiæøåÆøÅöôêÊ字符串 = { + /** + * Dummy input param + */ + nonAsciiParamæøåÆøÅöôêÊ: number; +}; + +@Injectable({ + providedIn: 'root', +}) +export class NonAsciiÆøåÆøÅöôêÊService { + constructor(public readonly http: HttpClient) {} + + /** + * @returns NonAsciiStringæøåÆØÅöôêÊ字符串 Successful response + * @throws ApiError + */ + public nonAsciiæøåÆøÅöôêÊ字符串( + data: TDataNonAsciiæøåÆøÅöôêÊ字符串 + ): Observable> { + const { nonAsciiParamæøåÆøÅöôêÊ } = data; + return __request(OpenAPI, this.http, { + method: 'POST', + url: '/api/v{api-version}/non-ascii-æøåÆØÅöôêÊ字符串', + query: { + nonAsciiParamæøåÆØÅöôêÊ: nonAsciiParamæøåÆøÅöôêÊ, + }, + }); + } +} diff --git a/test/__snapshots__/v3_angular/services/ParametersService.ts.snap b/test/__snapshots__/v3_angular/services/ParametersService.ts.snap new file mode 100644 index 000000000..972d92a97 --- /dev/null +++ b/test/__snapshots__/v3_angular/services/ParametersService.ts.snap @@ -0,0 +1,240 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import type { Observable } from 'rxjs'; +import type { ModelWithNestedArrayEnumsDataFoo } from '../models'; +import type { ModelWithOneOfEnum } from '../models'; +import type { ModelWithString } from '../models'; +import type { Pageable } from '../models'; +import { OpenAPI } from '../core/OpenAPI'; +import { request as __request } from '../core/request'; + +export type TDataDeleteFoo = { + /** + * bar in method + */ + bar: string; + /** + * foo in method + */ + foo: string; +}; +export type TDataCallWithParameters = { + fooAllOfEnum: ModelWithNestedArrayEnumsDataFoo; + fooRefEnum?: ModelWithNestedArrayEnumsDataFoo; + /** + * This is the parameter that goes into the cookie + */ + parameterCookie: string | null; + /** + * This is the parameter that goes into the form data + */ + parameterForm: 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 query params + */ + parameterQuery: string | null; + /** + * This is the parameter that goes into the body + */ + requestBody: ModelWithString | null; +}; +export type TDataCallWithWeirdParameterNames = { + /** + * 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 form data + */ + parameterForm: 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 TDataGetCallWithOptionalParam = { + /** + * This is an optional parameter + */ + parameter?: string; + /** + * This is a required parameter + */ + requestBody: ModelWithOneOfEnum; +}; +export type TDataPostCallWithOptionalParam = { + /** + * This is a required parameter + */ + parameter: Pageable; + /** + * This is an optional parameter + */ + requestBody?: ModelWithString; +}; + +@Injectable({ + providedIn: 'root', +}) +export class ParametersService { + constructor(public readonly http: HttpClient) {} + + /** + * @throws ApiError + */ + public deleteFoo(data: TDataDeleteFoo): Observable { + const { bar, foo } = data; + return __request(OpenAPI, this.http, { + method: 'DELETE', + url: '/api/v{api-version}/foo/{foo}/bar/{bar}', + path: { + foo, + bar, + }, + }); + } + + /** + * @throws ApiError + */ + public callWithParameters(data: TDataCallWithParameters): Observable { + const { + fooAllOfEnum, + fooRefEnum, + parameterCookie, + parameterForm, + parameterHeader, + parameterPath, + parameterQuery, + requestBody, + } = data; + return __request(OpenAPI, this.http, { + method: 'POST', + url: '/api/v{api-version}/parameters/{parameterPath}', + path: { + parameterPath, + }, + cookies: { + parameterCookie, + }, + headers: { + parameterHeader, + }, + query: { + foo_ref_enum: fooRefEnum, + foo_all_of_enum: fooAllOfEnum, + parameterQuery, + }, + formData: { + parameterForm, + }, + body: requestBody, + mediaType: 'application/json', + }); + } + + /** + * @throws ApiError + */ + public callWithWeirdParameterNames(data: TDataCallWithWeirdParameterNames): Observable { + const { + _default, + parameterCookie, + parameterForm, + parameterHeader, + parameterPath1, + parameterPath2, + parameterPath3, + parameterQuery, + requestBody, + } = data; + 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': parameterPath1, + 'parameter-path-2': parameterPath2, + 'PARAMETER-PATH-3': parameterPath3, + }, + cookies: { + 'PARAMETER-COOKIE': parameterCookie, + }, + headers: { + 'parameter.header': parameterHeader, + }, + query: { + default: _default, + 'parameter-query': parameterQuery, + }, + formData: { + parameter_form: parameterForm, + }, + body: requestBody, + mediaType: 'application/json', + }); + } + + /** + * @throws ApiError + */ + public getCallWithOptionalParam(data: TDataGetCallWithOptionalParam): Observable { + const { parameter, requestBody } = data; + return __request(OpenAPI, this.http, { + method: 'GET', + url: '/api/v{api-version}/parameters/', + query: { + parameter, + }, + body: requestBody, + mediaType: 'application/json', + }); + } + + /** + * @throws ApiError + */ + public postCallWithOptionalParam(data: TDataPostCallWithOptionalParam): Observable { + const { parameter, requestBody } = data; + return __request(OpenAPI, this.http, { + method: 'POST', + url: '/api/v{api-version}/parameters/', + query: { + parameter, + }, + body: requestBody, + mediaType: 'application/json', + }); + } +} diff --git a/test/__snapshots__/v3_angular/services/RequestBodyService.ts.snap b/test/__snapshots__/v3_angular/services/RequestBodyService.ts.snap new file mode 100644 index 000000000..7338ef5a4 --- /dev/null +++ b/test/__snapshots__/v3_angular/services/RequestBodyService.ts.snap @@ -0,0 +1,40 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import type { Observable } from 'rxjs'; +import type { ModelWithString } from '../models'; +import { OpenAPI } from '../core/OpenAPI'; +import { request as __request } from '../core/request'; + +export type TDataPostApiRequestBody = { + /** + * A reusable request body + */ + foo?: ModelWithString; + /** + * This is a reusable parameter + */ + parameter?: string; +}; + +@Injectable({ + providedIn: 'root', +}) +export class RequestBodyService { + constructor(public readonly http: HttpClient) {} + + /** + * @throws ApiError + */ + public postApiRequestBody(data: TDataPostApiRequestBody = {}): Observable { + const { foo, parameter } = data; + return __request(OpenAPI, this.http, { + method: 'POST', + url: '/api/v{api-version}/requestBody/', + query: { + parameter, + }, + body: foo, + mediaType: 'application/json', + }); + } +} diff --git a/test/__snapshots__/v3_angular/services/ResponseService.ts.snap b/test/__snapshots__/v3_angular/services/ResponseService.ts.snap new file mode 100644 index 000000000..5971aa984 --- /dev/null +++ b/test/__snapshots__/v3_angular/services/ResponseService.ts.snap @@ -0,0 +1,82 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import type { Observable } from 'rxjs'; +import type { ModelThatExtends } from '../models'; +import type { ModelThatExtendsExtends } from '../models'; +import type { ModelWithString } from '../models'; +import { OpenAPI } from '../core/OpenAPI'; +import { request as __request } from '../core/request'; + +@Injectable({ + providedIn: 'root', +}) +export class ResponseService { + constructor(public readonly http: HttpClient) {} + + /** + * @returns number Response is a simple number + * @returns void Success + * @throws ApiError + */ + public callWithResponseAndNoContentResponse(): Observable { + return __request(OpenAPI, this.http, { + method: 'GET', + url: '/api/v{api-version}/multiple-tags/response-and-no-content', + }); + } + + /** + * @returns ModelWithString + * @throws ApiError + */ + public callWithResponse(): Observable { + return __request(OpenAPI, this.http, { + method: 'GET', + url: '/api/v{api-version}/response', + }); + } + + /** + * @returns ModelWithString Message for default response + * @throws ApiError + */ + public callWithDuplicateResponses(): 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`, + }, + }); + } + + /** + * @returns any Message for 200 response + * @returns ModelWithString Message for default response + * @returns ModelThatExtends Message for 201 response + * @returns ModelThatExtendsExtends Message for 202 response + * @throws ApiError + */ + public callWithResponses(): Observable< + | { + readonly '@namespace.string'?: string; + readonly '@namespace.integer'?: number; + readonly value?: Array; + } + | ModelWithString + | ModelThatExtends + | ModelThatExtendsExtends + > { + 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`, + }, + }); + } +} diff --git a/test/__snapshots__/v3_angular/services/SimpleService.ts.snap b/test/__snapshots__/v3_angular/services/SimpleService.ts.snap new file mode 100644 index 000000000..6ae527765 --- /dev/null +++ b/test/__snapshots__/v3_angular/services/SimpleService.ts.snap @@ -0,0 +1,83 @@ +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'; + +@Injectable({ + providedIn: 'root', +}) +export class SimpleService { + constructor(public readonly http: HttpClient) {} + + /** + * @throws ApiError + */ + public getCallWithoutParametersAndResponse(): Observable { + return __request(OpenAPI, this.http, { + method: 'GET', + url: '/api/v{api-version}/simple', + }); + } + + /** + * @throws ApiError + */ + public putCallWithoutParametersAndResponse(): Observable { + return __request(OpenAPI, this.http, { + method: 'PUT', + url: '/api/v{api-version}/simple', + }); + } + + /** + * @throws ApiError + */ + public postCallWithoutParametersAndResponse(): Observable { + return __request(OpenAPI, this.http, { + method: 'POST', + url: '/api/v{api-version}/simple', + }); + } + + /** + * @throws ApiError + */ + public deleteCallWithoutParametersAndResponse(): Observable { + return __request(OpenAPI, this.http, { + method: 'DELETE', + url: '/api/v{api-version}/simple', + }); + } + + /** + * @throws ApiError + */ + public optionsCallWithoutParametersAndResponse(): Observable { + return __request(OpenAPI, this.http, { + method: 'OPTIONS', + url: '/api/v{api-version}/simple', + }); + } + + /** + * @throws ApiError + */ + public headCallWithoutParametersAndResponse(): Observable { + return __request(OpenAPI, this.http, { + method: 'HEAD', + url: '/api/v{api-version}/simple', + }); + } + + /** + * @throws ApiError + */ + public patchCallWithoutParametersAndResponse(): Observable { + return __request(OpenAPI, this.http, { + method: 'PATCH', + url: '/api/v{api-version}/simple', + }); + } +} diff --git a/test/__snapshots__/v3_angular/services/TypesService.ts.snap b/test/__snapshots__/v3_angular/services/TypesService.ts.snap new file mode 100644 index 000000000..a72f87f76 --- /dev/null +++ b/test/__snapshots__/v3_angular/services/TypesService.ts.snap @@ -0,0 +1,84 @@ +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'; + +export type TDataTypes = { + /** + * This is a number parameter + */ + id?: number; + /** + * This is an array parameter + */ + parameterArray: Array | null; + /** + * This is a boolean parameter + */ + parameterBoolean?: boolean | null; + /** + * This is a dictionary parameter + */ + parameterDictionary: Record | null; + /** + * This is an enum parameter + */ + parameterEnum: 'Success' | 'Warning' | 'Error' | null; + /** + * This is a number parameter + */ + parameterNumber?: number; + /** + * This is an object parameter + */ + parameterObject?: Record | null; + /** + * This is a string parameter + */ + parameterString?: string | null; +}; + +@Injectable({ + providedIn: 'root', +}) +export class TypesService { + constructor(public readonly http: HttpClient) {} + + /** + * @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 + */ + public types(data: TDataTypes): Observable> { + const { + id, + parameterArray, + parameterBoolean = true, + parameterDictionary, + parameterEnum, + parameterNumber = 123, + parameterObject = null, + parameterString = 'default', + } = data; + return __request(OpenAPI, this.http, { + method: 'GET', + url: '/api/v{api-version}/types', + path: { + id, + }, + query: { + parameterNumber, + parameterString, + parameterBoolean, + parameterObject, + parameterArray, + parameterDictionary, + parameterEnum, + }, + }); + } +} diff --git a/test/__snapshots__/v3_angular/services/UploadService.ts.snap b/test/__snapshots__/v3_angular/services/UploadService.ts.snap new file mode 100644 index 000000000..aa86ccb9e --- /dev/null +++ b/test/__snapshots__/v3_angular/services/UploadService.ts.snap @@ -0,0 +1,35 @@ +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'; + +export type TDataUploadFile = { + /** + * Supply a file reference for upload + */ + file: Blob; +}; + +@Injectable({ + providedIn: 'root', +}) +export class UploadService { + constructor(public readonly http: HttpClient) {} + + /** + * @returns boolean + * @throws ApiError + */ + public uploadFile(data: TDataUploadFile): Observable { + const { file } = data; + return __request(OpenAPI, this.http, { + method: 'POST', + url: '/api/v{api-version}/upload', + formData: { + file, + }, + }); + } +} diff --git a/test/__snapshots__/v3_angular/services/index.ts.snap b/test/__snapshots__/v3_angular/services/index.ts.snap new file mode 100644 index 000000000..3243a2fb1 --- /dev/null +++ b/test/__snapshots__/v3_angular/services/index.ts.snap @@ -0,0 +1,23 @@ +export { CollectionFormatService } from './CollectionFormatService'; +export { ComplexService } from './ComplexService'; +export { DefaultService } from './DefaultService'; +export { DefaultsService } from './DefaultsService'; +export { DeprecatedService } from './DeprecatedService'; +export { DescriptionsService } from './DescriptionsService'; +export { DuplicateService } from './DuplicateService'; +export { ErrorService } from './ErrorService'; +export { FileResponseService } from './FileResponseService'; +export { FormDataService } from './FormDataService'; +export { HeaderService } from './HeaderService'; +export { MultipartService } from './MultipartService'; +export { MultipleTags1Service } from './MultipleTags1Service'; +export { MultipleTags2Service } from './MultipleTags2Service'; +export { MultipleTags3Service } from './MultipleTags3Service'; +export { NoContentService } from './NoContentService'; +export { NonAsciiÆøåÆøÅöôêÊService } from './NonAsciiÆøåÆøÅöôêÊService'; +export { ParametersService } from './ParametersService'; +export { RequestBodyService } from './RequestBodyService'; +export { ResponseService } from './ResponseService'; +export { SimpleService } from './SimpleService'; +export { TypesService } from './TypesService'; +export { UploadService } from './UploadService'; diff --git a/test/e2e/client.angular.spec.ts b/test/e2e/client.angular.spec.ts index e80acd79d..d24cdb6f1 100644 --- a/test/e2e/client.angular.spec.ts +++ b/test/e2e/client.angular.spec.ts @@ -7,7 +7,7 @@ import { copyAsset } from './scripts/copyAsset'; import { generateClient } from './scripts/generateClient'; import server from './scripts/server'; -describe.skip('client.angular', () => { +describe('client.angular', () => { beforeAll(async () => { cleanup('client/angular'); createAngularProject('client/angular', 'app'); diff --git a/test/index.spec.ts b/test/index.spec.ts index d4f812ca1..d534a8d0f 100644 --- a/test/index.spec.ts +++ b/test/index.spec.ts @@ -114,6 +114,19 @@ describe('OpenAPI v3', () => { useLegacyEnums: true, } as UserConfig, }, + { + description: 'Should generate an angular client', + name: 'v3_angular', + config: { + client: 'angular', + enums: false, + exportCore: true, + exportModels: true, + exportSchemas: true, + exportServices: true, + useOptions: true, + } as UserConfig, + }, ])('$description', async ({ name, config }) => { const output = toOutputPath(name); await createClient({