fork of hey-api/openapi-ts because I need some additional things
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Merge pull request #2221 from hey-api/fix/axios-client-prefer-json

fix(parser): prefer JSON media type

authored by

Lubos and committed by
GitHub
df037451 5d85d294

+2561 -57
+5
.changeset/rude-shirts-decide.md
··· 1 + --- 2 + '@hey-api/openapi-ts': patch 3 + --- 4 + 5 + fix(parser): prefer JSON media type
+12
packages/openapi-ts-tests/test/3.0.x.test.ts
··· 161 161 }, 162 162 { 163 163 config: createConfig({ 164 + input: 'content-types.yaml', 165 + output: 'content-types', 166 + plugins: [ 167 + '@hey-api/client-axios', 168 + '@hey-api/typescript', 169 + '@hey-api/sdk', 170 + ], 171 + }), 172 + description: 'handles content types', 173 + }, 174 + { 175 + config: createConfig({ 164 176 input: 'discriminator-all-of.yaml', 165 177 output: 'discriminator-all-of', 166 178 }),
+12
packages/openapi-ts-tests/test/3.1.x.test.ts
··· 168 168 }, 169 169 { 170 170 config: createConfig({ 171 + input: 'content-types.yaml', 172 + output: 'content-types', 173 + plugins: [ 174 + '@hey-api/client-axios', 175 + '@hey-api/typescript', 176 + '@hey-api/sdk', 177 + ], 178 + }), 179 + description: 'handles content types', 180 + }, 181 + { 182 + config: createConfig({ 171 183 input: 'discriminator-all-of.yaml', 172 184 output: 'discriminator-all-of', 173 185 }),
+16
packages/openapi-ts-tests/test/__snapshots__/3.0.x/content-types/client.gen.ts
··· 1 + // This file is auto-generated by @hey-api/openapi-ts 2 + 3 + import type { ClientOptions } from './types.gen'; 4 + import { type Config, type ClientOptions as DefaultClientOptions, createClient, createConfig } from './client'; 5 + 6 + /** 7 + * The `createClientConfig()` function will be called on client initialization 8 + * and the returned object will become the client's initial configuration. 9 + * 10 + * You may want to initialize your client this way instead of calling 11 + * `setConfig()`. This is useful for example if you're using Next.js 12 + * to ensure your client always has the correct values. 13 + */ 14 + export type CreateClientConfig<T extends DefaultClientOptions = ClientOptions> = (override?: Config<DefaultClientOptions & T>) => Config<Required<DefaultClientOptions> & T>; 15 + 16 + export const client = createClient(createConfig<ClientOptions>());
+111
packages/openapi-ts-tests/test/__snapshots__/3.0.x/content-types/client/client.ts
··· 1 + import type { AxiosError, RawAxiosRequestHeaders } from 'axios'; 2 + import axios from 'axios'; 3 + 4 + import type { Client, Config } from './types'; 5 + import { 6 + buildUrl, 7 + createConfig, 8 + mergeConfigs, 9 + mergeHeaders, 10 + setAuthParams, 11 + } from './utils'; 12 + 13 + export const createClient = (config: Config = {}): Client => { 14 + let _config = mergeConfigs(createConfig(), config); 15 + 16 + // eslint-disable-next-line @typescript-eslint/no-unused-vars 17 + const { auth, ...configWithoutAuth } = _config; 18 + const instance = axios.create(configWithoutAuth); 19 + 20 + const getConfig = (): Config => ({ ..._config }); 21 + 22 + const setConfig = (config: Config): Config => { 23 + _config = mergeConfigs(_config, config); 24 + instance.defaults = { 25 + ...instance.defaults, 26 + ..._config, 27 + // @ts-expect-error 28 + headers: mergeHeaders(instance.defaults.headers, _config.headers), 29 + }; 30 + return getConfig(); 31 + }; 32 + 33 + // @ts-expect-error 34 + const request: Client['request'] = async (options) => { 35 + const opts = { 36 + ..._config, 37 + ...options, 38 + axios: options.axios ?? _config.axios ?? instance, 39 + headers: mergeHeaders(_config.headers, options.headers), 40 + }; 41 + 42 + if (opts.security) { 43 + await setAuthParams({ 44 + ...opts, 45 + security: opts.security, 46 + }); 47 + } 48 + 49 + if (opts.body && opts.bodySerializer) { 50 + opts.body = opts.bodySerializer(opts.body); 51 + } 52 + 53 + const url = buildUrl(opts); 54 + 55 + try { 56 + // assign Axios here for consistency with fetch 57 + const _axios = opts.axios!; 58 + // eslint-disable-next-line @typescript-eslint/no-unused-vars 59 + const { auth, ...optsWithoutAuth } = opts; 60 + const response = await _axios({ 61 + ...optsWithoutAuth, 62 + baseURL: opts.baseURL as string, 63 + data: opts.body, 64 + headers: opts.headers as RawAxiosRequestHeaders, 65 + // let `paramsSerializer()` handle query params if it exists 66 + params: opts.paramsSerializer ? opts.query : undefined, 67 + url, 68 + }); 69 + 70 + let { data } = response; 71 + 72 + if (opts.responseType === 'json') { 73 + if (opts.responseValidator) { 74 + await opts.responseValidator(data); 75 + } 76 + 77 + if (opts.responseTransformer) { 78 + data = await opts.responseTransformer(data); 79 + } 80 + } 81 + 82 + return { 83 + ...response, 84 + data: data ?? {}, 85 + }; 86 + } catch (error) { 87 + const e = error as AxiosError; 88 + if (opts.throwOnError) { 89 + throw e; 90 + } 91 + // @ts-expect-error 92 + e.error = e.response?.data ?? {}; 93 + return e; 94 + } 95 + }; 96 + 97 + return { 98 + buildUrl, 99 + delete: (options) => request({ ...options, method: 'DELETE' }), 100 + get: (options) => request({ ...options, method: 'GET' }), 101 + getConfig, 102 + head: (options) => request({ ...options, method: 'HEAD' }), 103 + instance, 104 + options: (options) => request({ ...options, method: 'OPTIONS' }), 105 + patch: (options) => request({ ...options, method: 'PATCH' }), 106 + post: (options) => request({ ...options, method: 'POST' }), 107 + put: (options) => request({ ...options, method: 'PUT' }), 108 + request, 109 + setConfig, 110 + } as Client; 111 + };
+21
packages/openapi-ts-tests/test/__snapshots__/3.0.x/content-types/client/index.ts
··· 1 + export type { Auth } from '../core/auth'; 2 + export type { QuerySerializerOptions } from '../core/bodySerializer'; 3 + export { 4 + formDataBodySerializer, 5 + jsonBodySerializer, 6 + urlSearchParamsBodySerializer, 7 + } from '../core/bodySerializer'; 8 + export { buildClientParams } from '../core/params'; 9 + export { createClient } from './client'; 10 + export type { 11 + Client, 12 + ClientOptions, 13 + Config, 14 + CreateClientConfig, 15 + Options, 16 + OptionsLegacyParser, 17 + RequestOptions, 18 + RequestResult, 19 + TDataShape, 20 + } from './types'; 21 + export { createConfig } from './utils';
+178
packages/openapi-ts-tests/test/__snapshots__/3.0.x/content-types/client/types.ts
··· 1 + import type { 2 + AxiosError, 3 + AxiosInstance, 4 + AxiosResponse, 5 + AxiosStatic, 6 + CreateAxiosDefaults, 7 + } from 'axios'; 8 + 9 + import type { Auth } from '../core/auth'; 10 + import type { 11 + Client as CoreClient, 12 + Config as CoreConfig, 13 + } from '../core/types'; 14 + 15 + export interface Config<T extends ClientOptions = ClientOptions> 16 + extends Omit<CreateAxiosDefaults, 'auth' | 'baseURL' | 'headers' | 'method'>, 17 + CoreConfig { 18 + /** 19 + * Axios implementation. You can use this option to provide a custom 20 + * Axios instance. 21 + * 22 + * @default axios 23 + */ 24 + axios?: AxiosStatic; 25 + /** 26 + * Base URL for all requests made by this client. 27 + */ 28 + baseURL?: T['baseURL']; 29 + /** 30 + * An object containing any HTTP headers that you want to pre-populate your 31 + * `Headers` object with. 32 + * 33 + * {@link https://developer.mozilla.org/docs/Web/API/Headers/Headers#init See more} 34 + */ 35 + headers?: 36 + | CreateAxiosDefaults['headers'] 37 + | Record< 38 + string, 39 + | string 40 + | number 41 + | boolean 42 + | (string | number | boolean)[] 43 + | null 44 + | undefined 45 + | unknown 46 + >; 47 + /** 48 + * Throw an error instead of returning it in the response? 49 + * 50 + * @default false 51 + */ 52 + throwOnError?: T['throwOnError']; 53 + } 54 + 55 + export interface RequestOptions< 56 + ThrowOnError extends boolean = boolean, 57 + Url extends string = string, 58 + > extends Config<{ 59 + throwOnError: ThrowOnError; 60 + }> { 61 + /** 62 + * Any body that you want to add to your request. 63 + * 64 + * {@link https://developer.mozilla.org/docs/Web/API/fetch#body} 65 + */ 66 + body?: unknown; 67 + path?: Record<string, unknown>; 68 + query?: Record<string, unknown>; 69 + /** 70 + * Security mechanism(s) to use for the request. 71 + */ 72 + security?: ReadonlyArray<Auth>; 73 + url: Url; 74 + } 75 + 76 + export type RequestResult< 77 + TData = unknown, 78 + TError = unknown, 79 + ThrowOnError extends boolean = boolean, 80 + > = ThrowOnError extends true 81 + ? Promise< 82 + AxiosResponse< 83 + TData extends Record<string, unknown> ? TData[keyof TData] : TData 84 + > 85 + > 86 + : Promise< 87 + | (AxiosResponse< 88 + TData extends Record<string, unknown> ? TData[keyof TData] : TData 89 + > & { error: undefined }) 90 + | (AxiosError< 91 + TError extends Record<string, unknown> ? TError[keyof TError] : TError 92 + > & { 93 + data: undefined; 94 + error: TError extends Record<string, unknown> 95 + ? TError[keyof TError] 96 + : TError; 97 + }) 98 + >; 99 + 100 + export interface ClientOptions { 101 + baseURL?: string; 102 + throwOnError?: boolean; 103 + } 104 + 105 + type MethodFn = < 106 + TData = unknown, 107 + TError = unknown, 108 + ThrowOnError extends boolean = false, 109 + >( 110 + options: Omit<RequestOptions<ThrowOnError>, 'method'>, 111 + ) => RequestResult<TData, TError, ThrowOnError>; 112 + 113 + type RequestFn = < 114 + TData = unknown, 115 + TError = unknown, 116 + ThrowOnError extends boolean = false, 117 + >( 118 + options: Omit<RequestOptions<ThrowOnError>, 'method'> & 119 + Pick<Required<RequestOptions<ThrowOnError>>, 'method'>, 120 + ) => RequestResult<TData, TError, ThrowOnError>; 121 + 122 + type BuildUrlFn = < 123 + TData extends { 124 + body?: unknown; 125 + path?: Record<string, unknown>; 126 + query?: Record<string, unknown>; 127 + url: string; 128 + }, 129 + >( 130 + options: Pick<TData, 'url'> & Omit<Options<TData>, 'axios'>, 131 + ) => string; 132 + 133 + export type Client = CoreClient<RequestFn, Config, MethodFn, BuildUrlFn> & { 134 + instance: AxiosInstance; 135 + }; 136 + 137 + /** 138 + * The `createClientConfig()` function will be called on client initialization 139 + * and the returned object will become the client's initial configuration. 140 + * 141 + * You may want to initialize your client this way instead of calling 142 + * `setConfig()`. This is useful for example if you're using Next.js 143 + * to ensure your client always has the correct values. 144 + */ 145 + export type CreateClientConfig<T extends ClientOptions = ClientOptions> = ( 146 + override?: Config<ClientOptions & T>, 147 + ) => Config<Required<ClientOptions> & T>; 148 + 149 + export interface TDataShape { 150 + body?: unknown; 151 + headers?: unknown; 152 + path?: unknown; 153 + query?: unknown; 154 + url: string; 155 + } 156 + 157 + type OmitKeys<T, K> = Pick<T, Exclude<keyof T, K>>; 158 + 159 + export type Options< 160 + TData extends TDataShape = TDataShape, 161 + ThrowOnError extends boolean = boolean, 162 + > = OmitKeys<RequestOptions<ThrowOnError>, 'body' | 'path' | 'query' | 'url'> & 163 + Omit<TData, 'url'>; 164 + 165 + export type OptionsLegacyParser< 166 + TData = unknown, 167 + ThrowOnError extends boolean = boolean, 168 + > = TData extends { body?: any } 169 + ? TData extends { headers?: any } 170 + ? OmitKeys<RequestOptions<ThrowOnError>, 'body' | 'headers' | 'url'> & TData 171 + : OmitKeys<RequestOptions<ThrowOnError>, 'body' | 'url'> & 172 + TData & 173 + Pick<RequestOptions<ThrowOnError>, 'headers'> 174 + : TData extends { headers?: any } 175 + ? OmitKeys<RequestOptions<ThrowOnError>, 'headers' | 'url'> & 176 + TData & 177 + Pick<RequestOptions<ThrowOnError>, 'body'> 178 + : OmitKeys<RequestOptions<ThrowOnError>, 'url'> & TData;
+286
packages/openapi-ts-tests/test/__snapshots__/3.0.x/content-types/client/utils.ts
··· 1 + import { getAuthToken } from '../core/auth'; 2 + import type { 3 + QuerySerializer, 4 + QuerySerializerOptions, 5 + } from '../core/bodySerializer'; 6 + import type { ArraySeparatorStyle } from '../core/pathSerializer'; 7 + import { 8 + serializeArrayParam, 9 + serializeObjectParam, 10 + serializePrimitiveParam, 11 + } from '../core/pathSerializer'; 12 + import type { Client, ClientOptions, Config, RequestOptions } from './types'; 13 + 14 + interface PathSerializer { 15 + path: Record<string, unknown>; 16 + url: string; 17 + } 18 + 19 + const PATH_PARAM_RE = /\{[^{}]+\}/g; 20 + 21 + const defaultPathSerializer = ({ path, url: _url }: PathSerializer) => { 22 + let url = _url; 23 + const matches = _url.match(PATH_PARAM_RE); 24 + if (matches) { 25 + for (const match of matches) { 26 + let explode = false; 27 + let name = match.substring(1, match.length - 1); 28 + let style: ArraySeparatorStyle = 'simple'; 29 + 30 + if (name.endsWith('*')) { 31 + explode = true; 32 + name = name.substring(0, name.length - 1); 33 + } 34 + 35 + if (name.startsWith('.')) { 36 + name = name.substring(1); 37 + style = 'label'; 38 + } else if (name.startsWith(';')) { 39 + name = name.substring(1); 40 + style = 'matrix'; 41 + } 42 + 43 + const value = path[name]; 44 + 45 + if (value === undefined || value === null) { 46 + continue; 47 + } 48 + 49 + if (Array.isArray(value)) { 50 + url = url.replace( 51 + match, 52 + serializeArrayParam({ explode, name, style, value }), 53 + ); 54 + continue; 55 + } 56 + 57 + if (typeof value === 'object') { 58 + url = url.replace( 59 + match, 60 + serializeObjectParam({ 61 + explode, 62 + name, 63 + style, 64 + value: value as Record<string, unknown>, 65 + valueOnly: true, 66 + }), 67 + ); 68 + continue; 69 + } 70 + 71 + if (style === 'matrix') { 72 + url = url.replace( 73 + match, 74 + `;${serializePrimitiveParam({ 75 + name, 76 + value: value as string, 77 + })}`, 78 + ); 79 + continue; 80 + } 81 + 82 + const replaceValue = encodeURIComponent( 83 + style === 'label' ? `.${value as string}` : (value as string), 84 + ); 85 + url = url.replace(match, replaceValue); 86 + } 87 + } 88 + return url; 89 + }; 90 + 91 + export const createQuerySerializer = <T = unknown>({ 92 + allowReserved, 93 + array, 94 + object, 95 + }: QuerySerializerOptions = {}) => { 96 + const querySerializer = (queryParams: T) => { 97 + const search: string[] = []; 98 + if (queryParams && typeof queryParams === 'object') { 99 + for (const name in queryParams) { 100 + const value = queryParams[name]; 101 + 102 + if (value === undefined || value === null) { 103 + continue; 104 + } 105 + 106 + if (Array.isArray(value)) { 107 + const serializedArray = serializeArrayParam({ 108 + allowReserved, 109 + explode: true, 110 + name, 111 + style: 'form', 112 + value, 113 + ...array, 114 + }); 115 + if (serializedArray) search.push(serializedArray); 116 + } else if (typeof value === 'object') { 117 + const serializedObject = serializeObjectParam({ 118 + allowReserved, 119 + explode: true, 120 + name, 121 + style: 'deepObject', 122 + value: value as Record<string, unknown>, 123 + ...object, 124 + }); 125 + if (serializedObject) search.push(serializedObject); 126 + } else { 127 + const serializedPrimitive = serializePrimitiveParam({ 128 + allowReserved, 129 + name, 130 + value: value as string, 131 + }); 132 + if (serializedPrimitive) search.push(serializedPrimitive); 133 + } 134 + } 135 + } 136 + return search.join('&'); 137 + }; 138 + return querySerializer; 139 + }; 140 + 141 + export const setAuthParams = async ({ 142 + security, 143 + ...options 144 + }: Pick<Required<RequestOptions>, 'security'> & 145 + Pick<RequestOptions, 'auth' | 'query'> & { 146 + headers: Record<any, unknown>; 147 + }) => { 148 + for (const auth of security) { 149 + const token = await getAuthToken(auth, options.auth); 150 + 151 + if (!token) { 152 + continue; 153 + } 154 + 155 + const name = auth.name ?? 'Authorization'; 156 + 157 + switch (auth.in) { 158 + case 'query': 159 + if (!options.query) { 160 + options.query = {}; 161 + } 162 + options.query[name] = token; 163 + break; 164 + case 'cookie': { 165 + const value = `${name}=${token}`; 166 + if ('Cookie' in options.headers && options.headers['Cookie']) { 167 + options.headers['Cookie'] = `${options.headers['Cookie']}; ${value}`; 168 + } else { 169 + options.headers['Cookie'] = value; 170 + } 171 + break; 172 + } 173 + case 'header': 174 + default: 175 + options.headers[name] = token; 176 + break; 177 + } 178 + 179 + return; 180 + } 181 + }; 182 + 183 + export const buildUrl: Client['buildUrl'] = (options) => { 184 + const url = getUrl({ 185 + path: options.path, 186 + // let `paramsSerializer()` handle query params if it exists 187 + query: !options.paramsSerializer ? options.query : undefined, 188 + querySerializer: 189 + typeof options.querySerializer === 'function' 190 + ? options.querySerializer 191 + : createQuerySerializer(options.querySerializer), 192 + url: options.url, 193 + }); 194 + return url; 195 + }; 196 + 197 + export const getUrl = ({ 198 + path, 199 + query, 200 + querySerializer, 201 + url: _url, 202 + }: { 203 + path?: Record<string, unknown>; 204 + query?: Record<string, unknown>; 205 + querySerializer: QuerySerializer; 206 + url: string; 207 + }) => { 208 + const pathUrl = _url.startsWith('/') ? _url : `/${_url}`; 209 + let url = pathUrl; 210 + if (path) { 211 + url = defaultPathSerializer({ path, url }); 212 + } 213 + let search = query ? querySerializer(query) : ''; 214 + if (search.startsWith('?')) { 215 + search = search.substring(1); 216 + } 217 + if (search) { 218 + url += `?${search}`; 219 + } 220 + return url; 221 + }; 222 + 223 + export const mergeConfigs = (a: Config, b: Config): Config => { 224 + const config = { ...a, ...b }; 225 + config.headers = mergeHeaders(a.headers, b.headers); 226 + return config; 227 + }; 228 + 229 + /** 230 + * Special Axios headers keywords allowing to set headers by request method. 231 + */ 232 + export const axiosHeadersKeywords = [ 233 + 'common', 234 + 'delete', 235 + 'get', 236 + 'head', 237 + 'patch', 238 + 'post', 239 + 'put', 240 + ] as const; 241 + 242 + export const mergeHeaders = ( 243 + ...headers: Array<Required<Config>['headers'] | undefined> 244 + ): Record<any, unknown> => { 245 + const mergedHeaders: Record<any, unknown> = {}; 246 + for (const header of headers) { 247 + if (!header || typeof header !== 'object') { 248 + continue; 249 + } 250 + 251 + const iterator = Object.entries(header); 252 + 253 + for (const [key, value] of iterator) { 254 + if ( 255 + axiosHeadersKeywords.includes( 256 + key as (typeof axiosHeadersKeywords)[number], 257 + ) && 258 + typeof value === 'object' 259 + ) { 260 + mergedHeaders[key] = { 261 + ...(mergedHeaders[key] as Record<any, unknown>), 262 + ...value, 263 + }; 264 + } else if (value === null) { 265 + delete mergedHeaders[key]; 266 + } else if (Array.isArray(value)) { 267 + for (const v of value) { 268 + // @ts-expect-error 269 + mergedHeaders[key] = [...(mergedHeaders[key] ?? []), v as string]; 270 + } 271 + } else if (value !== undefined) { 272 + // assume object headers are meant to be JSON stringified, i.e. their 273 + // content value in OpenAPI specification is 'application/json' 274 + mergedHeaders[key] = 275 + typeof value === 'object' ? JSON.stringify(value) : (value as string); 276 + } 277 + } 278 + } 279 + return mergedHeaders; 280 + }; 281 + 282 + export const createConfig = <T extends ClientOptions = ClientOptions>( 283 + override: Config<Omit<ClientOptions, keyof T> & T> = {}, 284 + ): Config<Omit<ClientOptions, keyof T> & T> => ({ 285 + ...override, 286 + });
+40
packages/openapi-ts-tests/test/__snapshots__/3.0.x/content-types/core/auth.ts
··· 1 + export type AuthToken = string | undefined; 2 + 3 + export interface Auth { 4 + /** 5 + * Which part of the request do we use to send the auth? 6 + * 7 + * @default 'header' 8 + */ 9 + in?: 'header' | 'query' | 'cookie'; 10 + /** 11 + * Header or query parameter name. 12 + * 13 + * @default 'Authorization' 14 + */ 15 + name?: string; 16 + scheme?: 'basic' | 'bearer'; 17 + type: 'apiKey' | 'http'; 18 + } 19 + 20 + export const getAuthToken = async ( 21 + auth: Auth, 22 + callback: ((auth: Auth) => Promise<AuthToken> | AuthToken) | AuthToken, 23 + ): Promise<string | undefined> => { 24 + const token = 25 + typeof callback === 'function' ? await callback(auth) : callback; 26 + 27 + if (!token) { 28 + return; 29 + } 30 + 31 + if (auth.scheme === 'bearer') { 32 + return `Bearer ${token}`; 33 + } 34 + 35 + if (auth.scheme === 'basic') { 36 + return `Basic ${btoa(token)}`; 37 + } 38 + 39 + return token; 40 + };
+84
packages/openapi-ts-tests/test/__snapshots__/3.0.x/content-types/core/bodySerializer.ts
··· 1 + import type { 2 + ArrayStyle, 3 + ObjectStyle, 4 + SerializerOptions, 5 + } from './pathSerializer'; 6 + 7 + export type QuerySerializer = (query: Record<string, unknown>) => string; 8 + 9 + export type BodySerializer = (body: any) => any; 10 + 11 + export interface QuerySerializerOptions { 12 + allowReserved?: boolean; 13 + array?: SerializerOptions<ArrayStyle>; 14 + object?: SerializerOptions<ObjectStyle>; 15 + } 16 + 17 + const serializeFormDataPair = (data: FormData, key: string, value: unknown) => { 18 + if (typeof value === 'string' || value instanceof Blob) { 19 + data.append(key, value); 20 + } else { 21 + data.append(key, JSON.stringify(value)); 22 + } 23 + }; 24 + 25 + const serializeUrlSearchParamsPair = ( 26 + data: URLSearchParams, 27 + key: string, 28 + value: unknown, 29 + ) => { 30 + if (typeof value === 'string') { 31 + data.append(key, value); 32 + } else { 33 + data.append(key, JSON.stringify(value)); 34 + } 35 + }; 36 + 37 + export const formDataBodySerializer = { 38 + bodySerializer: <T extends Record<string, any> | Array<Record<string, any>>>( 39 + body: T, 40 + ) => { 41 + const data = new FormData(); 42 + 43 + Object.entries(body).forEach(([key, value]) => { 44 + if (value === undefined || value === null) { 45 + return; 46 + } 47 + if (Array.isArray(value)) { 48 + value.forEach((v) => serializeFormDataPair(data, key, v)); 49 + } else { 50 + serializeFormDataPair(data, key, value); 51 + } 52 + }); 53 + 54 + return data; 55 + }, 56 + }; 57 + 58 + export const jsonBodySerializer = { 59 + bodySerializer: <T>(body: T) => 60 + JSON.stringify(body, (_key, value) => 61 + typeof value === 'bigint' ? value.toString() : value, 62 + ), 63 + }; 64 + 65 + export const urlSearchParamsBodySerializer = { 66 + bodySerializer: <T extends Record<string, any> | Array<Record<string, any>>>( 67 + body: T, 68 + ) => { 69 + const data = new URLSearchParams(); 70 + 71 + Object.entries(body).forEach(([key, value]) => { 72 + if (value === undefined || value === null) { 73 + return; 74 + } 75 + if (Array.isArray(value)) { 76 + value.forEach((v) => serializeUrlSearchParamsPair(data, key, v)); 77 + } else { 78 + serializeUrlSearchParamsPair(data, key, value); 79 + } 80 + }); 81 + 82 + return data.toString(); 83 + }, 84 + };
+141
packages/openapi-ts-tests/test/__snapshots__/3.0.x/content-types/core/params.ts
··· 1 + type Slot = 'body' | 'headers' | 'path' | 'query'; 2 + 3 + export type Field = 4 + | { 5 + in: Exclude<Slot, 'body'>; 6 + key: string; 7 + map?: string; 8 + } 9 + | { 10 + in: Extract<Slot, 'body'>; 11 + key?: string; 12 + map?: string; 13 + }; 14 + 15 + export interface Fields { 16 + allowExtra?: Partial<Record<Slot, boolean>>; 17 + args?: ReadonlyArray<Field>; 18 + } 19 + 20 + export type FieldsConfig = ReadonlyArray<Field | Fields>; 21 + 22 + const extraPrefixesMap: Record<string, Slot> = { 23 + $body_: 'body', 24 + $headers_: 'headers', 25 + $path_: 'path', 26 + $query_: 'query', 27 + }; 28 + const extraPrefixes = Object.entries(extraPrefixesMap); 29 + 30 + type KeyMap = Map< 31 + string, 32 + { 33 + in: Slot; 34 + map?: string; 35 + } 36 + >; 37 + 38 + const buildKeyMap = (fields: FieldsConfig, map?: KeyMap): KeyMap => { 39 + if (!map) { 40 + map = new Map(); 41 + } 42 + 43 + for (const config of fields) { 44 + if ('in' in config) { 45 + if (config.key) { 46 + map.set(config.key, { 47 + in: config.in, 48 + map: config.map, 49 + }); 50 + } 51 + } else if (config.args) { 52 + buildKeyMap(config.args, map); 53 + } 54 + } 55 + 56 + return map; 57 + }; 58 + 59 + interface Params { 60 + body: unknown; 61 + headers: Record<string, unknown>; 62 + path: Record<string, unknown>; 63 + query: Record<string, unknown>; 64 + } 65 + 66 + const stripEmptySlots = (params: Params) => { 67 + for (const [slot, value] of Object.entries(params)) { 68 + if (value && typeof value === 'object' && !Object.keys(value).length) { 69 + delete params[slot as Slot]; 70 + } 71 + } 72 + }; 73 + 74 + export const buildClientParams = ( 75 + args: ReadonlyArray<unknown>, 76 + fields: FieldsConfig, 77 + ) => { 78 + const params: Params = { 79 + body: {}, 80 + headers: {}, 81 + path: {}, 82 + query: {}, 83 + }; 84 + 85 + const map = buildKeyMap(fields); 86 + 87 + let config: FieldsConfig[number] | undefined; 88 + 89 + for (const [index, arg] of args.entries()) { 90 + if (fields[index]) { 91 + config = fields[index]; 92 + } 93 + 94 + if (!config) { 95 + continue; 96 + } 97 + 98 + if ('in' in config) { 99 + if (config.key) { 100 + const field = map.get(config.key)!; 101 + const name = field.map || config.key; 102 + (params[field.in] as Record<string, unknown>)[name] = arg; 103 + } else { 104 + params.body = arg; 105 + } 106 + } else { 107 + for (const [key, value] of Object.entries(arg ?? {})) { 108 + const field = map.get(key); 109 + 110 + if (field) { 111 + const name = field.map || key; 112 + (params[field.in] as Record<string, unknown>)[name] = value; 113 + } else { 114 + const extra = extraPrefixes.find(([prefix]) => 115 + key.startsWith(prefix), 116 + ); 117 + 118 + if (extra) { 119 + const [prefix, slot] = extra; 120 + (params[slot] as Record<string, unknown>)[ 121 + key.slice(prefix.length) 122 + ] = value; 123 + } else { 124 + for (const [slot, allowed] of Object.entries( 125 + config.allowExtra ?? {}, 126 + )) { 127 + if (allowed) { 128 + (params[slot as Slot] as Record<string, unknown>)[key] = value; 129 + break; 130 + } 131 + } 132 + } 133 + } 134 + } 135 + } 136 + } 137 + 138 + stripEmptySlots(params); 139 + 140 + return params; 141 + };
+179
packages/openapi-ts-tests/test/__snapshots__/3.0.x/content-types/core/pathSerializer.ts
··· 1 + interface SerializeOptions<T> 2 + extends SerializePrimitiveOptions, 3 + SerializerOptions<T> {} 4 + 5 + interface SerializePrimitiveOptions { 6 + allowReserved?: boolean; 7 + name: string; 8 + } 9 + 10 + export interface SerializerOptions<T> { 11 + /** 12 + * @default true 13 + */ 14 + explode: boolean; 15 + style: T; 16 + } 17 + 18 + export type ArrayStyle = 'form' | 'spaceDelimited' | 'pipeDelimited'; 19 + export type ArraySeparatorStyle = ArrayStyle | MatrixStyle; 20 + type MatrixStyle = 'label' | 'matrix' | 'simple'; 21 + export type ObjectStyle = 'form' | 'deepObject'; 22 + type ObjectSeparatorStyle = ObjectStyle | MatrixStyle; 23 + 24 + interface SerializePrimitiveParam extends SerializePrimitiveOptions { 25 + value: string; 26 + } 27 + 28 + export const separatorArrayExplode = (style: ArraySeparatorStyle) => { 29 + switch (style) { 30 + case 'label': 31 + return '.'; 32 + case 'matrix': 33 + return ';'; 34 + case 'simple': 35 + return ','; 36 + default: 37 + return '&'; 38 + } 39 + }; 40 + 41 + export const separatorArrayNoExplode = (style: ArraySeparatorStyle) => { 42 + switch (style) { 43 + case 'form': 44 + return ','; 45 + case 'pipeDelimited': 46 + return '|'; 47 + case 'spaceDelimited': 48 + return '%20'; 49 + default: 50 + return ','; 51 + } 52 + }; 53 + 54 + export const separatorObjectExplode = (style: ObjectSeparatorStyle) => { 55 + switch (style) { 56 + case 'label': 57 + return '.'; 58 + case 'matrix': 59 + return ';'; 60 + case 'simple': 61 + return ','; 62 + default: 63 + return '&'; 64 + } 65 + }; 66 + 67 + export const serializeArrayParam = ({ 68 + allowReserved, 69 + explode, 70 + name, 71 + style, 72 + value, 73 + }: SerializeOptions<ArraySeparatorStyle> & { 74 + value: unknown[]; 75 + }) => { 76 + if (!explode) { 77 + const joinedValues = ( 78 + allowReserved ? value : value.map((v) => encodeURIComponent(v as string)) 79 + ).join(separatorArrayNoExplode(style)); 80 + switch (style) { 81 + case 'label': 82 + return `.${joinedValues}`; 83 + case 'matrix': 84 + return `;${name}=${joinedValues}`; 85 + case 'simple': 86 + return joinedValues; 87 + default: 88 + return `${name}=${joinedValues}`; 89 + } 90 + } 91 + 92 + const separator = separatorArrayExplode(style); 93 + const joinedValues = value 94 + .map((v) => { 95 + if (style === 'label' || style === 'simple') { 96 + return allowReserved ? v : encodeURIComponent(v as string); 97 + } 98 + 99 + return serializePrimitiveParam({ 100 + allowReserved, 101 + name, 102 + value: v as string, 103 + }); 104 + }) 105 + .join(separator); 106 + return style === 'label' || style === 'matrix' 107 + ? separator + joinedValues 108 + : joinedValues; 109 + }; 110 + 111 + export const serializePrimitiveParam = ({ 112 + allowReserved, 113 + name, 114 + value, 115 + }: SerializePrimitiveParam) => { 116 + if (value === undefined || value === null) { 117 + return ''; 118 + } 119 + 120 + if (typeof value === 'object') { 121 + throw new Error( 122 + 'Deeply-nested arrays/objects aren’t supported. Provide your own `querySerializer()` to handle these.', 123 + ); 124 + } 125 + 126 + return `${name}=${allowReserved ? value : encodeURIComponent(value)}`; 127 + }; 128 + 129 + export const serializeObjectParam = ({ 130 + allowReserved, 131 + explode, 132 + name, 133 + style, 134 + value, 135 + valueOnly, 136 + }: SerializeOptions<ObjectSeparatorStyle> & { 137 + value: Record<string, unknown> | Date; 138 + valueOnly?: boolean; 139 + }) => { 140 + if (value instanceof Date) { 141 + return valueOnly ? value.toISOString() : `${name}=${value.toISOString()}`; 142 + } 143 + 144 + if (style !== 'deepObject' && !explode) { 145 + let values: string[] = []; 146 + Object.entries(value).forEach(([key, v]) => { 147 + values = [ 148 + ...values, 149 + key, 150 + allowReserved ? (v as string) : encodeURIComponent(v as string), 151 + ]; 152 + }); 153 + const joinedValues = values.join(','); 154 + switch (style) { 155 + case 'form': 156 + return `${name}=${joinedValues}`; 157 + case 'label': 158 + return `.${joinedValues}`; 159 + case 'matrix': 160 + return `;${name}=${joinedValues}`; 161 + default: 162 + return joinedValues; 163 + } 164 + } 165 + 166 + const separator = separatorObjectExplode(style); 167 + const joinedValues = Object.entries(value) 168 + .map(([key, v]) => 169 + serializePrimitiveParam({ 170 + allowReserved, 171 + name: style === 'deepObject' ? `${name}[${key}]` : key, 172 + value: v as string, 173 + }), 174 + ) 175 + .join(separator); 176 + return style === 'label' || style === 'matrix' 177 + ? separator + joinedValues 178 + : joinedValues; 179 + };
+98
packages/openapi-ts-tests/test/__snapshots__/3.0.x/content-types/core/types.ts
··· 1 + import type { Auth, AuthToken } from './auth'; 2 + import type { 3 + BodySerializer, 4 + QuerySerializer, 5 + QuerySerializerOptions, 6 + } from './bodySerializer'; 7 + 8 + export interface Client< 9 + RequestFn = never, 10 + Config = unknown, 11 + MethodFn = never, 12 + BuildUrlFn = never, 13 + > { 14 + /** 15 + * Returns the final request URL. 16 + */ 17 + buildUrl: BuildUrlFn; 18 + connect: MethodFn; 19 + delete: MethodFn; 20 + get: MethodFn; 21 + getConfig: () => Config; 22 + head: MethodFn; 23 + options: MethodFn; 24 + patch: MethodFn; 25 + post: MethodFn; 26 + put: MethodFn; 27 + request: RequestFn; 28 + setConfig: (config: Config) => Config; 29 + trace: MethodFn; 30 + } 31 + 32 + export interface Config { 33 + /** 34 + * Auth token or a function returning auth token. The resolved value will be 35 + * added to the request payload as defined by its `security` array. 36 + */ 37 + auth?: ((auth: Auth) => Promise<AuthToken> | AuthToken) | AuthToken; 38 + /** 39 + * A function for serializing request body parameter. By default, 40 + * {@link JSON.stringify()} will be used. 41 + */ 42 + bodySerializer?: BodySerializer | null; 43 + /** 44 + * An object containing any HTTP headers that you want to pre-populate your 45 + * `Headers` object with. 46 + * 47 + * {@link https://developer.mozilla.org/docs/Web/API/Headers/Headers#init See more} 48 + */ 49 + headers?: 50 + | RequestInit['headers'] 51 + | Record< 52 + string, 53 + | string 54 + | number 55 + | boolean 56 + | (string | number | boolean)[] 57 + | null 58 + | undefined 59 + | unknown 60 + >; 61 + /** 62 + * The request method. 63 + * 64 + * {@link https://developer.mozilla.org/docs/Web/API/fetch#method See more} 65 + */ 66 + method?: 67 + | 'CONNECT' 68 + | 'DELETE' 69 + | 'GET' 70 + | 'HEAD' 71 + | 'OPTIONS' 72 + | 'PATCH' 73 + | 'POST' 74 + | 'PUT' 75 + | 'TRACE'; 76 + /** 77 + * A function for serializing request query parameters. By default, arrays 78 + * will be exploded in form style, objects will be exploded in deepObject 79 + * style, and reserved characters are percent-encoded. 80 + * 81 + * This method will have no effect if the native `paramsSerializer()` Axios 82 + * API function is used. 83 + * 84 + * {@link https://swagger.io/docs/specification/serialization/#query View examples} 85 + */ 86 + querySerializer?: QuerySerializer | QuerySerializerOptions; 87 + /** 88 + * A function transforming response data before it's returned. This is useful 89 + * for post-processing data, e.g. converting ISO strings into Date objects. 90 + */ 91 + responseTransformer?: (data: unknown) => Promise<unknown>; 92 + /** 93 + * A function validating response data. This is useful if you want to ensure 94 + * the response conforms to the desired shape, so it can be safely passed to 95 + * the transformers and returned to the user. 96 + */ 97 + responseValidator?: (data: unknown) => Promise<unknown>; 98 + }
+3
packages/openapi-ts-tests/test/__snapshots__/3.0.x/content-types/index.ts
··· 1 + // This file is auto-generated by @hey-api/openapi-ts 2 + export * from './types.gen'; 3 + export * from './sdk.gen';
+27
packages/openapi-ts-tests/test/__snapshots__/3.0.x/content-types/sdk.gen.ts
··· 1 + // This file is auto-generated by @hey-api/openapi-ts 2 + 3 + import type { Options as ClientOptions, TDataShape, Client } from './client'; 4 + import type { GetFooData, GetFooResponses } from './types.gen'; 5 + import { client as _heyApiClient } from './client.gen'; 6 + 7 + export type Options<TData extends TDataShape = TDataShape, ThrowOnError extends boolean = boolean> = ClientOptions<TData, ThrowOnError> & { 8 + /** 9 + * You can provide a client instance returned by `createClient()` instead of 10 + * individual options. This might be also useful if you want to implement a 11 + * custom client. 12 + */ 13 + client?: Client; 14 + /** 15 + * You can pass arbitrary values through the `meta` object. This can be 16 + * used to access values that aren't defined as part of the SDK function. 17 + */ 18 + meta?: Record<string, unknown>; 19 + }; 20 + 21 + export const getFoo = <ThrowOnError extends boolean = false>(options?: Options<GetFooData, ThrowOnError>) => { 22 + return (options?.client ?? _heyApiClient).get<GetFooResponses, unknown, ThrowOnError>({ 23 + responseType: 'json', 24 + url: '/foo', 25 + ...options 26 + }); 27 + };
+21
packages/openapi-ts-tests/test/__snapshots__/3.0.x/content-types/types.gen.ts
··· 1 + // This file is auto-generated by @hey-api/openapi-ts 2 + 3 + export type GetFooData = { 4 + body?: never; 5 + path?: never; 6 + query?: never; 7 + url: '/foo'; 8 + }; 9 + 10 + export type GetFooResponses = { 11 + /** 12 + * OK 13 + */ 14 + 200: string; 15 + }; 16 + 17 + export type GetFooResponse = GetFooResponses[keyof GetFooResponses]; 18 + 19 + export type ClientOptions = { 20 + baseURL: `${string}://${string}` | (string & {}); 21 + };
+16
packages/openapi-ts-tests/test/__snapshots__/3.1.x/content-types/client.gen.ts
··· 1 + // This file is auto-generated by @hey-api/openapi-ts 2 + 3 + import type { ClientOptions } from './types.gen'; 4 + import { type Config, type ClientOptions as DefaultClientOptions, createClient, createConfig } from './client'; 5 + 6 + /** 7 + * The `createClientConfig()` function will be called on client initialization 8 + * and the returned object will become the client's initial configuration. 9 + * 10 + * You may want to initialize your client this way instead of calling 11 + * `setConfig()`. This is useful for example if you're using Next.js 12 + * to ensure your client always has the correct values. 13 + */ 14 + export type CreateClientConfig<T extends DefaultClientOptions = ClientOptions> = (override?: Config<DefaultClientOptions & T>) => Config<Required<DefaultClientOptions> & T>; 15 + 16 + export const client = createClient(createConfig<ClientOptions>());
+111
packages/openapi-ts-tests/test/__snapshots__/3.1.x/content-types/client/client.ts
··· 1 + import type { AxiosError, RawAxiosRequestHeaders } from 'axios'; 2 + import axios from 'axios'; 3 + 4 + import type { Client, Config } from './types'; 5 + import { 6 + buildUrl, 7 + createConfig, 8 + mergeConfigs, 9 + mergeHeaders, 10 + setAuthParams, 11 + } from './utils'; 12 + 13 + export const createClient = (config: Config = {}): Client => { 14 + let _config = mergeConfigs(createConfig(), config); 15 + 16 + // eslint-disable-next-line @typescript-eslint/no-unused-vars 17 + const { auth, ...configWithoutAuth } = _config; 18 + const instance = axios.create(configWithoutAuth); 19 + 20 + const getConfig = (): Config => ({ ..._config }); 21 + 22 + const setConfig = (config: Config): Config => { 23 + _config = mergeConfigs(_config, config); 24 + instance.defaults = { 25 + ...instance.defaults, 26 + ..._config, 27 + // @ts-expect-error 28 + headers: mergeHeaders(instance.defaults.headers, _config.headers), 29 + }; 30 + return getConfig(); 31 + }; 32 + 33 + // @ts-expect-error 34 + const request: Client['request'] = async (options) => { 35 + const opts = { 36 + ..._config, 37 + ...options, 38 + axios: options.axios ?? _config.axios ?? instance, 39 + headers: mergeHeaders(_config.headers, options.headers), 40 + }; 41 + 42 + if (opts.security) { 43 + await setAuthParams({ 44 + ...opts, 45 + security: opts.security, 46 + }); 47 + } 48 + 49 + if (opts.body && opts.bodySerializer) { 50 + opts.body = opts.bodySerializer(opts.body); 51 + } 52 + 53 + const url = buildUrl(opts); 54 + 55 + try { 56 + // assign Axios here for consistency with fetch 57 + const _axios = opts.axios!; 58 + // eslint-disable-next-line @typescript-eslint/no-unused-vars 59 + const { auth, ...optsWithoutAuth } = opts; 60 + const response = await _axios({ 61 + ...optsWithoutAuth, 62 + baseURL: opts.baseURL as string, 63 + data: opts.body, 64 + headers: opts.headers as RawAxiosRequestHeaders, 65 + // let `paramsSerializer()` handle query params if it exists 66 + params: opts.paramsSerializer ? opts.query : undefined, 67 + url, 68 + }); 69 + 70 + let { data } = response; 71 + 72 + if (opts.responseType === 'json') { 73 + if (opts.responseValidator) { 74 + await opts.responseValidator(data); 75 + } 76 + 77 + if (opts.responseTransformer) { 78 + data = await opts.responseTransformer(data); 79 + } 80 + } 81 + 82 + return { 83 + ...response, 84 + data: data ?? {}, 85 + }; 86 + } catch (error) { 87 + const e = error as AxiosError; 88 + if (opts.throwOnError) { 89 + throw e; 90 + } 91 + // @ts-expect-error 92 + e.error = e.response?.data ?? {}; 93 + return e; 94 + } 95 + }; 96 + 97 + return { 98 + buildUrl, 99 + delete: (options) => request({ ...options, method: 'DELETE' }), 100 + get: (options) => request({ ...options, method: 'GET' }), 101 + getConfig, 102 + head: (options) => request({ ...options, method: 'HEAD' }), 103 + instance, 104 + options: (options) => request({ ...options, method: 'OPTIONS' }), 105 + patch: (options) => request({ ...options, method: 'PATCH' }), 106 + post: (options) => request({ ...options, method: 'POST' }), 107 + put: (options) => request({ ...options, method: 'PUT' }), 108 + request, 109 + setConfig, 110 + } as Client; 111 + };
+21
packages/openapi-ts-tests/test/__snapshots__/3.1.x/content-types/client/index.ts
··· 1 + export type { Auth } from '../core/auth'; 2 + export type { QuerySerializerOptions } from '../core/bodySerializer'; 3 + export { 4 + formDataBodySerializer, 5 + jsonBodySerializer, 6 + urlSearchParamsBodySerializer, 7 + } from '../core/bodySerializer'; 8 + export { buildClientParams } from '../core/params'; 9 + export { createClient } from './client'; 10 + export type { 11 + Client, 12 + ClientOptions, 13 + Config, 14 + CreateClientConfig, 15 + Options, 16 + OptionsLegacyParser, 17 + RequestOptions, 18 + RequestResult, 19 + TDataShape, 20 + } from './types'; 21 + export { createConfig } from './utils';
+178
packages/openapi-ts-tests/test/__snapshots__/3.1.x/content-types/client/types.ts
··· 1 + import type { 2 + AxiosError, 3 + AxiosInstance, 4 + AxiosResponse, 5 + AxiosStatic, 6 + CreateAxiosDefaults, 7 + } from 'axios'; 8 + 9 + import type { Auth } from '../core/auth'; 10 + import type { 11 + Client as CoreClient, 12 + Config as CoreConfig, 13 + } from '../core/types'; 14 + 15 + export interface Config<T extends ClientOptions = ClientOptions> 16 + extends Omit<CreateAxiosDefaults, 'auth' | 'baseURL' | 'headers' | 'method'>, 17 + CoreConfig { 18 + /** 19 + * Axios implementation. You can use this option to provide a custom 20 + * Axios instance. 21 + * 22 + * @default axios 23 + */ 24 + axios?: AxiosStatic; 25 + /** 26 + * Base URL for all requests made by this client. 27 + */ 28 + baseURL?: T['baseURL']; 29 + /** 30 + * An object containing any HTTP headers that you want to pre-populate your 31 + * `Headers` object with. 32 + * 33 + * {@link https://developer.mozilla.org/docs/Web/API/Headers/Headers#init See more} 34 + */ 35 + headers?: 36 + | CreateAxiosDefaults['headers'] 37 + | Record< 38 + string, 39 + | string 40 + | number 41 + | boolean 42 + | (string | number | boolean)[] 43 + | null 44 + | undefined 45 + | unknown 46 + >; 47 + /** 48 + * Throw an error instead of returning it in the response? 49 + * 50 + * @default false 51 + */ 52 + throwOnError?: T['throwOnError']; 53 + } 54 + 55 + export interface RequestOptions< 56 + ThrowOnError extends boolean = boolean, 57 + Url extends string = string, 58 + > extends Config<{ 59 + throwOnError: ThrowOnError; 60 + }> { 61 + /** 62 + * Any body that you want to add to your request. 63 + * 64 + * {@link https://developer.mozilla.org/docs/Web/API/fetch#body} 65 + */ 66 + body?: unknown; 67 + path?: Record<string, unknown>; 68 + query?: Record<string, unknown>; 69 + /** 70 + * Security mechanism(s) to use for the request. 71 + */ 72 + security?: ReadonlyArray<Auth>; 73 + url: Url; 74 + } 75 + 76 + export type RequestResult< 77 + TData = unknown, 78 + TError = unknown, 79 + ThrowOnError extends boolean = boolean, 80 + > = ThrowOnError extends true 81 + ? Promise< 82 + AxiosResponse< 83 + TData extends Record<string, unknown> ? TData[keyof TData] : TData 84 + > 85 + > 86 + : Promise< 87 + | (AxiosResponse< 88 + TData extends Record<string, unknown> ? TData[keyof TData] : TData 89 + > & { error: undefined }) 90 + | (AxiosError< 91 + TError extends Record<string, unknown> ? TError[keyof TError] : TError 92 + > & { 93 + data: undefined; 94 + error: TError extends Record<string, unknown> 95 + ? TError[keyof TError] 96 + : TError; 97 + }) 98 + >; 99 + 100 + export interface ClientOptions { 101 + baseURL?: string; 102 + throwOnError?: boolean; 103 + } 104 + 105 + type MethodFn = < 106 + TData = unknown, 107 + TError = unknown, 108 + ThrowOnError extends boolean = false, 109 + >( 110 + options: Omit<RequestOptions<ThrowOnError>, 'method'>, 111 + ) => RequestResult<TData, TError, ThrowOnError>; 112 + 113 + type RequestFn = < 114 + TData = unknown, 115 + TError = unknown, 116 + ThrowOnError extends boolean = false, 117 + >( 118 + options: Omit<RequestOptions<ThrowOnError>, 'method'> & 119 + Pick<Required<RequestOptions<ThrowOnError>>, 'method'>, 120 + ) => RequestResult<TData, TError, ThrowOnError>; 121 + 122 + type BuildUrlFn = < 123 + TData extends { 124 + body?: unknown; 125 + path?: Record<string, unknown>; 126 + query?: Record<string, unknown>; 127 + url: string; 128 + }, 129 + >( 130 + options: Pick<TData, 'url'> & Omit<Options<TData>, 'axios'>, 131 + ) => string; 132 + 133 + export type Client = CoreClient<RequestFn, Config, MethodFn, BuildUrlFn> & { 134 + instance: AxiosInstance; 135 + }; 136 + 137 + /** 138 + * The `createClientConfig()` function will be called on client initialization 139 + * and the returned object will become the client's initial configuration. 140 + * 141 + * You may want to initialize your client this way instead of calling 142 + * `setConfig()`. This is useful for example if you're using Next.js 143 + * to ensure your client always has the correct values. 144 + */ 145 + export type CreateClientConfig<T extends ClientOptions = ClientOptions> = ( 146 + override?: Config<ClientOptions & T>, 147 + ) => Config<Required<ClientOptions> & T>; 148 + 149 + export interface TDataShape { 150 + body?: unknown; 151 + headers?: unknown; 152 + path?: unknown; 153 + query?: unknown; 154 + url: string; 155 + } 156 + 157 + type OmitKeys<T, K> = Pick<T, Exclude<keyof T, K>>; 158 + 159 + export type Options< 160 + TData extends TDataShape = TDataShape, 161 + ThrowOnError extends boolean = boolean, 162 + > = OmitKeys<RequestOptions<ThrowOnError>, 'body' | 'path' | 'query' | 'url'> & 163 + Omit<TData, 'url'>; 164 + 165 + export type OptionsLegacyParser< 166 + TData = unknown, 167 + ThrowOnError extends boolean = boolean, 168 + > = TData extends { body?: any } 169 + ? TData extends { headers?: any } 170 + ? OmitKeys<RequestOptions<ThrowOnError>, 'body' | 'headers' | 'url'> & TData 171 + : OmitKeys<RequestOptions<ThrowOnError>, 'body' | 'url'> & 172 + TData & 173 + Pick<RequestOptions<ThrowOnError>, 'headers'> 174 + : TData extends { headers?: any } 175 + ? OmitKeys<RequestOptions<ThrowOnError>, 'headers' | 'url'> & 176 + TData & 177 + Pick<RequestOptions<ThrowOnError>, 'body'> 178 + : OmitKeys<RequestOptions<ThrowOnError>, 'url'> & TData;
+286
packages/openapi-ts-tests/test/__snapshots__/3.1.x/content-types/client/utils.ts
··· 1 + import { getAuthToken } from '../core/auth'; 2 + import type { 3 + QuerySerializer, 4 + QuerySerializerOptions, 5 + } from '../core/bodySerializer'; 6 + import type { ArraySeparatorStyle } from '../core/pathSerializer'; 7 + import { 8 + serializeArrayParam, 9 + serializeObjectParam, 10 + serializePrimitiveParam, 11 + } from '../core/pathSerializer'; 12 + import type { Client, ClientOptions, Config, RequestOptions } from './types'; 13 + 14 + interface PathSerializer { 15 + path: Record<string, unknown>; 16 + url: string; 17 + } 18 + 19 + const PATH_PARAM_RE = /\{[^{}]+\}/g; 20 + 21 + const defaultPathSerializer = ({ path, url: _url }: PathSerializer) => { 22 + let url = _url; 23 + const matches = _url.match(PATH_PARAM_RE); 24 + if (matches) { 25 + for (const match of matches) { 26 + let explode = false; 27 + let name = match.substring(1, match.length - 1); 28 + let style: ArraySeparatorStyle = 'simple'; 29 + 30 + if (name.endsWith('*')) { 31 + explode = true; 32 + name = name.substring(0, name.length - 1); 33 + } 34 + 35 + if (name.startsWith('.')) { 36 + name = name.substring(1); 37 + style = 'label'; 38 + } else if (name.startsWith(';')) { 39 + name = name.substring(1); 40 + style = 'matrix'; 41 + } 42 + 43 + const value = path[name]; 44 + 45 + if (value === undefined || value === null) { 46 + continue; 47 + } 48 + 49 + if (Array.isArray(value)) { 50 + url = url.replace( 51 + match, 52 + serializeArrayParam({ explode, name, style, value }), 53 + ); 54 + continue; 55 + } 56 + 57 + if (typeof value === 'object') { 58 + url = url.replace( 59 + match, 60 + serializeObjectParam({ 61 + explode, 62 + name, 63 + style, 64 + value: value as Record<string, unknown>, 65 + valueOnly: true, 66 + }), 67 + ); 68 + continue; 69 + } 70 + 71 + if (style === 'matrix') { 72 + url = url.replace( 73 + match, 74 + `;${serializePrimitiveParam({ 75 + name, 76 + value: value as string, 77 + })}`, 78 + ); 79 + continue; 80 + } 81 + 82 + const replaceValue = encodeURIComponent( 83 + style === 'label' ? `.${value as string}` : (value as string), 84 + ); 85 + url = url.replace(match, replaceValue); 86 + } 87 + } 88 + return url; 89 + }; 90 + 91 + export const createQuerySerializer = <T = unknown>({ 92 + allowReserved, 93 + array, 94 + object, 95 + }: QuerySerializerOptions = {}) => { 96 + const querySerializer = (queryParams: T) => { 97 + const search: string[] = []; 98 + if (queryParams && typeof queryParams === 'object') { 99 + for (const name in queryParams) { 100 + const value = queryParams[name]; 101 + 102 + if (value === undefined || value === null) { 103 + continue; 104 + } 105 + 106 + if (Array.isArray(value)) { 107 + const serializedArray = serializeArrayParam({ 108 + allowReserved, 109 + explode: true, 110 + name, 111 + style: 'form', 112 + value, 113 + ...array, 114 + }); 115 + if (serializedArray) search.push(serializedArray); 116 + } else if (typeof value === 'object') { 117 + const serializedObject = serializeObjectParam({ 118 + allowReserved, 119 + explode: true, 120 + name, 121 + style: 'deepObject', 122 + value: value as Record<string, unknown>, 123 + ...object, 124 + }); 125 + if (serializedObject) search.push(serializedObject); 126 + } else { 127 + const serializedPrimitive = serializePrimitiveParam({ 128 + allowReserved, 129 + name, 130 + value: value as string, 131 + }); 132 + if (serializedPrimitive) search.push(serializedPrimitive); 133 + } 134 + } 135 + } 136 + return search.join('&'); 137 + }; 138 + return querySerializer; 139 + }; 140 + 141 + export const setAuthParams = async ({ 142 + security, 143 + ...options 144 + }: Pick<Required<RequestOptions>, 'security'> & 145 + Pick<RequestOptions, 'auth' | 'query'> & { 146 + headers: Record<any, unknown>; 147 + }) => { 148 + for (const auth of security) { 149 + const token = await getAuthToken(auth, options.auth); 150 + 151 + if (!token) { 152 + continue; 153 + } 154 + 155 + const name = auth.name ?? 'Authorization'; 156 + 157 + switch (auth.in) { 158 + case 'query': 159 + if (!options.query) { 160 + options.query = {}; 161 + } 162 + options.query[name] = token; 163 + break; 164 + case 'cookie': { 165 + const value = `${name}=${token}`; 166 + if ('Cookie' in options.headers && options.headers['Cookie']) { 167 + options.headers['Cookie'] = `${options.headers['Cookie']}; ${value}`; 168 + } else { 169 + options.headers['Cookie'] = value; 170 + } 171 + break; 172 + } 173 + case 'header': 174 + default: 175 + options.headers[name] = token; 176 + break; 177 + } 178 + 179 + return; 180 + } 181 + }; 182 + 183 + export const buildUrl: Client['buildUrl'] = (options) => { 184 + const url = getUrl({ 185 + path: options.path, 186 + // let `paramsSerializer()` handle query params if it exists 187 + query: !options.paramsSerializer ? options.query : undefined, 188 + querySerializer: 189 + typeof options.querySerializer === 'function' 190 + ? options.querySerializer 191 + : createQuerySerializer(options.querySerializer), 192 + url: options.url, 193 + }); 194 + return url; 195 + }; 196 + 197 + export const getUrl = ({ 198 + path, 199 + query, 200 + querySerializer, 201 + url: _url, 202 + }: { 203 + path?: Record<string, unknown>; 204 + query?: Record<string, unknown>; 205 + querySerializer: QuerySerializer; 206 + url: string; 207 + }) => { 208 + const pathUrl = _url.startsWith('/') ? _url : `/${_url}`; 209 + let url = pathUrl; 210 + if (path) { 211 + url = defaultPathSerializer({ path, url }); 212 + } 213 + let search = query ? querySerializer(query) : ''; 214 + if (search.startsWith('?')) { 215 + search = search.substring(1); 216 + } 217 + if (search) { 218 + url += `?${search}`; 219 + } 220 + return url; 221 + }; 222 + 223 + export const mergeConfigs = (a: Config, b: Config): Config => { 224 + const config = { ...a, ...b }; 225 + config.headers = mergeHeaders(a.headers, b.headers); 226 + return config; 227 + }; 228 + 229 + /** 230 + * Special Axios headers keywords allowing to set headers by request method. 231 + */ 232 + export const axiosHeadersKeywords = [ 233 + 'common', 234 + 'delete', 235 + 'get', 236 + 'head', 237 + 'patch', 238 + 'post', 239 + 'put', 240 + ] as const; 241 + 242 + export const mergeHeaders = ( 243 + ...headers: Array<Required<Config>['headers'] | undefined> 244 + ): Record<any, unknown> => { 245 + const mergedHeaders: Record<any, unknown> = {}; 246 + for (const header of headers) { 247 + if (!header || typeof header !== 'object') { 248 + continue; 249 + } 250 + 251 + const iterator = Object.entries(header); 252 + 253 + for (const [key, value] of iterator) { 254 + if ( 255 + axiosHeadersKeywords.includes( 256 + key as (typeof axiosHeadersKeywords)[number], 257 + ) && 258 + typeof value === 'object' 259 + ) { 260 + mergedHeaders[key] = { 261 + ...(mergedHeaders[key] as Record<any, unknown>), 262 + ...value, 263 + }; 264 + } else if (value === null) { 265 + delete mergedHeaders[key]; 266 + } else if (Array.isArray(value)) { 267 + for (const v of value) { 268 + // @ts-expect-error 269 + mergedHeaders[key] = [...(mergedHeaders[key] ?? []), v as string]; 270 + } 271 + } else if (value !== undefined) { 272 + // assume object headers are meant to be JSON stringified, i.e. their 273 + // content value in OpenAPI specification is 'application/json' 274 + mergedHeaders[key] = 275 + typeof value === 'object' ? JSON.stringify(value) : (value as string); 276 + } 277 + } 278 + } 279 + return mergedHeaders; 280 + }; 281 + 282 + export const createConfig = <T extends ClientOptions = ClientOptions>( 283 + override: Config<Omit<ClientOptions, keyof T> & T> = {}, 284 + ): Config<Omit<ClientOptions, keyof T> & T> => ({ 285 + ...override, 286 + });
+40
packages/openapi-ts-tests/test/__snapshots__/3.1.x/content-types/core/auth.ts
··· 1 + export type AuthToken = string | undefined; 2 + 3 + export interface Auth { 4 + /** 5 + * Which part of the request do we use to send the auth? 6 + * 7 + * @default 'header' 8 + */ 9 + in?: 'header' | 'query' | 'cookie'; 10 + /** 11 + * Header or query parameter name. 12 + * 13 + * @default 'Authorization' 14 + */ 15 + name?: string; 16 + scheme?: 'basic' | 'bearer'; 17 + type: 'apiKey' | 'http'; 18 + } 19 + 20 + export const getAuthToken = async ( 21 + auth: Auth, 22 + callback: ((auth: Auth) => Promise<AuthToken> | AuthToken) | AuthToken, 23 + ): Promise<string | undefined> => { 24 + const token = 25 + typeof callback === 'function' ? await callback(auth) : callback; 26 + 27 + if (!token) { 28 + return; 29 + } 30 + 31 + if (auth.scheme === 'bearer') { 32 + return `Bearer ${token}`; 33 + } 34 + 35 + if (auth.scheme === 'basic') { 36 + return `Basic ${btoa(token)}`; 37 + } 38 + 39 + return token; 40 + };
+84
packages/openapi-ts-tests/test/__snapshots__/3.1.x/content-types/core/bodySerializer.ts
··· 1 + import type { 2 + ArrayStyle, 3 + ObjectStyle, 4 + SerializerOptions, 5 + } from './pathSerializer'; 6 + 7 + export type QuerySerializer = (query: Record<string, unknown>) => string; 8 + 9 + export type BodySerializer = (body: any) => any; 10 + 11 + export interface QuerySerializerOptions { 12 + allowReserved?: boolean; 13 + array?: SerializerOptions<ArrayStyle>; 14 + object?: SerializerOptions<ObjectStyle>; 15 + } 16 + 17 + const serializeFormDataPair = (data: FormData, key: string, value: unknown) => { 18 + if (typeof value === 'string' || value instanceof Blob) { 19 + data.append(key, value); 20 + } else { 21 + data.append(key, JSON.stringify(value)); 22 + } 23 + }; 24 + 25 + const serializeUrlSearchParamsPair = ( 26 + data: URLSearchParams, 27 + key: string, 28 + value: unknown, 29 + ) => { 30 + if (typeof value === 'string') { 31 + data.append(key, value); 32 + } else { 33 + data.append(key, JSON.stringify(value)); 34 + } 35 + }; 36 + 37 + export const formDataBodySerializer = { 38 + bodySerializer: <T extends Record<string, any> | Array<Record<string, any>>>( 39 + body: T, 40 + ) => { 41 + const data = new FormData(); 42 + 43 + Object.entries(body).forEach(([key, value]) => { 44 + if (value === undefined || value === null) { 45 + return; 46 + } 47 + if (Array.isArray(value)) { 48 + value.forEach((v) => serializeFormDataPair(data, key, v)); 49 + } else { 50 + serializeFormDataPair(data, key, value); 51 + } 52 + }); 53 + 54 + return data; 55 + }, 56 + }; 57 + 58 + export const jsonBodySerializer = { 59 + bodySerializer: <T>(body: T) => 60 + JSON.stringify(body, (_key, value) => 61 + typeof value === 'bigint' ? value.toString() : value, 62 + ), 63 + }; 64 + 65 + export const urlSearchParamsBodySerializer = { 66 + bodySerializer: <T extends Record<string, any> | Array<Record<string, any>>>( 67 + body: T, 68 + ) => { 69 + const data = new URLSearchParams(); 70 + 71 + Object.entries(body).forEach(([key, value]) => { 72 + if (value === undefined || value === null) { 73 + return; 74 + } 75 + if (Array.isArray(value)) { 76 + value.forEach((v) => serializeUrlSearchParamsPair(data, key, v)); 77 + } else { 78 + serializeUrlSearchParamsPair(data, key, value); 79 + } 80 + }); 81 + 82 + return data.toString(); 83 + }, 84 + };
+141
packages/openapi-ts-tests/test/__snapshots__/3.1.x/content-types/core/params.ts
··· 1 + type Slot = 'body' | 'headers' | 'path' | 'query'; 2 + 3 + export type Field = 4 + | { 5 + in: Exclude<Slot, 'body'>; 6 + key: string; 7 + map?: string; 8 + } 9 + | { 10 + in: Extract<Slot, 'body'>; 11 + key?: string; 12 + map?: string; 13 + }; 14 + 15 + export interface Fields { 16 + allowExtra?: Partial<Record<Slot, boolean>>; 17 + args?: ReadonlyArray<Field>; 18 + } 19 + 20 + export type FieldsConfig = ReadonlyArray<Field | Fields>; 21 + 22 + const extraPrefixesMap: Record<string, Slot> = { 23 + $body_: 'body', 24 + $headers_: 'headers', 25 + $path_: 'path', 26 + $query_: 'query', 27 + }; 28 + const extraPrefixes = Object.entries(extraPrefixesMap); 29 + 30 + type KeyMap = Map< 31 + string, 32 + { 33 + in: Slot; 34 + map?: string; 35 + } 36 + >; 37 + 38 + const buildKeyMap = (fields: FieldsConfig, map?: KeyMap): KeyMap => { 39 + if (!map) { 40 + map = new Map(); 41 + } 42 + 43 + for (const config of fields) { 44 + if ('in' in config) { 45 + if (config.key) { 46 + map.set(config.key, { 47 + in: config.in, 48 + map: config.map, 49 + }); 50 + } 51 + } else if (config.args) { 52 + buildKeyMap(config.args, map); 53 + } 54 + } 55 + 56 + return map; 57 + }; 58 + 59 + interface Params { 60 + body: unknown; 61 + headers: Record<string, unknown>; 62 + path: Record<string, unknown>; 63 + query: Record<string, unknown>; 64 + } 65 + 66 + const stripEmptySlots = (params: Params) => { 67 + for (const [slot, value] of Object.entries(params)) { 68 + if (value && typeof value === 'object' && !Object.keys(value).length) { 69 + delete params[slot as Slot]; 70 + } 71 + } 72 + }; 73 + 74 + export const buildClientParams = ( 75 + args: ReadonlyArray<unknown>, 76 + fields: FieldsConfig, 77 + ) => { 78 + const params: Params = { 79 + body: {}, 80 + headers: {}, 81 + path: {}, 82 + query: {}, 83 + }; 84 + 85 + const map = buildKeyMap(fields); 86 + 87 + let config: FieldsConfig[number] | undefined; 88 + 89 + for (const [index, arg] of args.entries()) { 90 + if (fields[index]) { 91 + config = fields[index]; 92 + } 93 + 94 + if (!config) { 95 + continue; 96 + } 97 + 98 + if ('in' in config) { 99 + if (config.key) { 100 + const field = map.get(config.key)!; 101 + const name = field.map || config.key; 102 + (params[field.in] as Record<string, unknown>)[name] = arg; 103 + } else { 104 + params.body = arg; 105 + } 106 + } else { 107 + for (const [key, value] of Object.entries(arg ?? {})) { 108 + const field = map.get(key); 109 + 110 + if (field) { 111 + const name = field.map || key; 112 + (params[field.in] as Record<string, unknown>)[name] = value; 113 + } else { 114 + const extra = extraPrefixes.find(([prefix]) => 115 + key.startsWith(prefix), 116 + ); 117 + 118 + if (extra) { 119 + const [prefix, slot] = extra; 120 + (params[slot] as Record<string, unknown>)[ 121 + key.slice(prefix.length) 122 + ] = value; 123 + } else { 124 + for (const [slot, allowed] of Object.entries( 125 + config.allowExtra ?? {}, 126 + )) { 127 + if (allowed) { 128 + (params[slot as Slot] as Record<string, unknown>)[key] = value; 129 + break; 130 + } 131 + } 132 + } 133 + } 134 + } 135 + } 136 + } 137 + 138 + stripEmptySlots(params); 139 + 140 + return params; 141 + };
+179
packages/openapi-ts-tests/test/__snapshots__/3.1.x/content-types/core/pathSerializer.ts
··· 1 + interface SerializeOptions<T> 2 + extends SerializePrimitiveOptions, 3 + SerializerOptions<T> {} 4 + 5 + interface SerializePrimitiveOptions { 6 + allowReserved?: boolean; 7 + name: string; 8 + } 9 + 10 + export interface SerializerOptions<T> { 11 + /** 12 + * @default true 13 + */ 14 + explode: boolean; 15 + style: T; 16 + } 17 + 18 + export type ArrayStyle = 'form' | 'spaceDelimited' | 'pipeDelimited'; 19 + export type ArraySeparatorStyle = ArrayStyle | MatrixStyle; 20 + type MatrixStyle = 'label' | 'matrix' | 'simple'; 21 + export type ObjectStyle = 'form' | 'deepObject'; 22 + type ObjectSeparatorStyle = ObjectStyle | MatrixStyle; 23 + 24 + interface SerializePrimitiveParam extends SerializePrimitiveOptions { 25 + value: string; 26 + } 27 + 28 + export const separatorArrayExplode = (style: ArraySeparatorStyle) => { 29 + switch (style) { 30 + case 'label': 31 + return '.'; 32 + case 'matrix': 33 + return ';'; 34 + case 'simple': 35 + return ','; 36 + default: 37 + return '&'; 38 + } 39 + }; 40 + 41 + export const separatorArrayNoExplode = (style: ArraySeparatorStyle) => { 42 + switch (style) { 43 + case 'form': 44 + return ','; 45 + case 'pipeDelimited': 46 + return '|'; 47 + case 'spaceDelimited': 48 + return '%20'; 49 + default: 50 + return ','; 51 + } 52 + }; 53 + 54 + export const separatorObjectExplode = (style: ObjectSeparatorStyle) => { 55 + switch (style) { 56 + case 'label': 57 + return '.'; 58 + case 'matrix': 59 + return ';'; 60 + case 'simple': 61 + return ','; 62 + default: 63 + return '&'; 64 + } 65 + }; 66 + 67 + export const serializeArrayParam = ({ 68 + allowReserved, 69 + explode, 70 + name, 71 + style, 72 + value, 73 + }: SerializeOptions<ArraySeparatorStyle> & { 74 + value: unknown[]; 75 + }) => { 76 + if (!explode) { 77 + const joinedValues = ( 78 + allowReserved ? value : value.map((v) => encodeURIComponent(v as string)) 79 + ).join(separatorArrayNoExplode(style)); 80 + switch (style) { 81 + case 'label': 82 + return `.${joinedValues}`; 83 + case 'matrix': 84 + return `;${name}=${joinedValues}`; 85 + case 'simple': 86 + return joinedValues; 87 + default: 88 + return `${name}=${joinedValues}`; 89 + } 90 + } 91 + 92 + const separator = separatorArrayExplode(style); 93 + const joinedValues = value 94 + .map((v) => { 95 + if (style === 'label' || style === 'simple') { 96 + return allowReserved ? v : encodeURIComponent(v as string); 97 + } 98 + 99 + return serializePrimitiveParam({ 100 + allowReserved, 101 + name, 102 + value: v as string, 103 + }); 104 + }) 105 + .join(separator); 106 + return style === 'label' || style === 'matrix' 107 + ? separator + joinedValues 108 + : joinedValues; 109 + }; 110 + 111 + export const serializePrimitiveParam = ({ 112 + allowReserved, 113 + name, 114 + value, 115 + }: SerializePrimitiveParam) => { 116 + if (value === undefined || value === null) { 117 + return ''; 118 + } 119 + 120 + if (typeof value === 'object') { 121 + throw new Error( 122 + 'Deeply-nested arrays/objects aren’t supported. Provide your own `querySerializer()` to handle these.', 123 + ); 124 + } 125 + 126 + return `${name}=${allowReserved ? value : encodeURIComponent(value)}`; 127 + }; 128 + 129 + export const serializeObjectParam = ({ 130 + allowReserved, 131 + explode, 132 + name, 133 + style, 134 + value, 135 + valueOnly, 136 + }: SerializeOptions<ObjectSeparatorStyle> & { 137 + value: Record<string, unknown> | Date; 138 + valueOnly?: boolean; 139 + }) => { 140 + if (value instanceof Date) { 141 + return valueOnly ? value.toISOString() : `${name}=${value.toISOString()}`; 142 + } 143 + 144 + if (style !== 'deepObject' && !explode) { 145 + let values: string[] = []; 146 + Object.entries(value).forEach(([key, v]) => { 147 + values = [ 148 + ...values, 149 + key, 150 + allowReserved ? (v as string) : encodeURIComponent(v as string), 151 + ]; 152 + }); 153 + const joinedValues = values.join(','); 154 + switch (style) { 155 + case 'form': 156 + return `${name}=${joinedValues}`; 157 + case 'label': 158 + return `.${joinedValues}`; 159 + case 'matrix': 160 + return `;${name}=${joinedValues}`; 161 + default: 162 + return joinedValues; 163 + } 164 + } 165 + 166 + const separator = separatorObjectExplode(style); 167 + const joinedValues = Object.entries(value) 168 + .map(([key, v]) => 169 + serializePrimitiveParam({ 170 + allowReserved, 171 + name: style === 'deepObject' ? `${name}[${key}]` : key, 172 + value: v as string, 173 + }), 174 + ) 175 + .join(separator); 176 + return style === 'label' || style === 'matrix' 177 + ? separator + joinedValues 178 + : joinedValues; 179 + };
+98
packages/openapi-ts-tests/test/__snapshots__/3.1.x/content-types/core/types.ts
··· 1 + import type { Auth, AuthToken } from './auth'; 2 + import type { 3 + BodySerializer, 4 + QuerySerializer, 5 + QuerySerializerOptions, 6 + } from './bodySerializer'; 7 + 8 + export interface Client< 9 + RequestFn = never, 10 + Config = unknown, 11 + MethodFn = never, 12 + BuildUrlFn = never, 13 + > { 14 + /** 15 + * Returns the final request URL. 16 + */ 17 + buildUrl: BuildUrlFn; 18 + connect: MethodFn; 19 + delete: MethodFn; 20 + get: MethodFn; 21 + getConfig: () => Config; 22 + head: MethodFn; 23 + options: MethodFn; 24 + patch: MethodFn; 25 + post: MethodFn; 26 + put: MethodFn; 27 + request: RequestFn; 28 + setConfig: (config: Config) => Config; 29 + trace: MethodFn; 30 + } 31 + 32 + export interface Config { 33 + /** 34 + * Auth token or a function returning auth token. The resolved value will be 35 + * added to the request payload as defined by its `security` array. 36 + */ 37 + auth?: ((auth: Auth) => Promise<AuthToken> | AuthToken) | AuthToken; 38 + /** 39 + * A function for serializing request body parameter. By default, 40 + * {@link JSON.stringify()} will be used. 41 + */ 42 + bodySerializer?: BodySerializer | null; 43 + /** 44 + * An object containing any HTTP headers that you want to pre-populate your 45 + * `Headers` object with. 46 + * 47 + * {@link https://developer.mozilla.org/docs/Web/API/Headers/Headers#init See more} 48 + */ 49 + headers?: 50 + | RequestInit['headers'] 51 + | Record< 52 + string, 53 + | string 54 + | number 55 + | boolean 56 + | (string | number | boolean)[] 57 + | null 58 + | undefined 59 + | unknown 60 + >; 61 + /** 62 + * The request method. 63 + * 64 + * {@link https://developer.mozilla.org/docs/Web/API/fetch#method See more} 65 + */ 66 + method?: 67 + | 'CONNECT' 68 + | 'DELETE' 69 + | 'GET' 70 + | 'HEAD' 71 + | 'OPTIONS' 72 + | 'PATCH' 73 + | 'POST' 74 + | 'PUT' 75 + | 'TRACE'; 76 + /** 77 + * A function for serializing request query parameters. By default, arrays 78 + * will be exploded in form style, objects will be exploded in deepObject 79 + * style, and reserved characters are percent-encoded. 80 + * 81 + * This method will have no effect if the native `paramsSerializer()` Axios 82 + * API function is used. 83 + * 84 + * {@link https://swagger.io/docs/specification/serialization/#query View examples} 85 + */ 86 + querySerializer?: QuerySerializer | QuerySerializerOptions; 87 + /** 88 + * A function transforming response data before it's returned. This is useful 89 + * for post-processing data, e.g. converting ISO strings into Date objects. 90 + */ 91 + responseTransformer?: (data: unknown) => Promise<unknown>; 92 + /** 93 + * A function validating response data. This is useful if you want to ensure 94 + * the response conforms to the desired shape, so it can be safely passed to 95 + * the transformers and returned to the user. 96 + */ 97 + responseValidator?: (data: unknown) => Promise<unknown>; 98 + }
+3
packages/openapi-ts-tests/test/__snapshots__/3.1.x/content-types/index.ts
··· 1 + // This file is auto-generated by @hey-api/openapi-ts 2 + export * from './types.gen'; 3 + export * from './sdk.gen';
+27
packages/openapi-ts-tests/test/__snapshots__/3.1.x/content-types/sdk.gen.ts
··· 1 + // This file is auto-generated by @hey-api/openapi-ts 2 + 3 + import type { Options as ClientOptions, TDataShape, Client } from './client'; 4 + import type { GetFooData, GetFooResponses } from './types.gen'; 5 + import { client as _heyApiClient } from './client.gen'; 6 + 7 + export type Options<TData extends TDataShape = TDataShape, ThrowOnError extends boolean = boolean> = ClientOptions<TData, ThrowOnError> & { 8 + /** 9 + * You can provide a client instance returned by `createClient()` instead of 10 + * individual options. This might be also useful if you want to implement a 11 + * custom client. 12 + */ 13 + client?: Client; 14 + /** 15 + * You can pass arbitrary values through the `meta` object. This can be 16 + * used to access values that aren't defined as part of the SDK function. 17 + */ 18 + meta?: Record<string, unknown>; 19 + }; 20 + 21 + export const getFoo = <ThrowOnError extends boolean = false>(options?: Options<GetFooData, ThrowOnError>) => { 22 + return (options?.client ?? _heyApiClient).get<GetFooResponses, unknown, ThrowOnError>({ 23 + responseType: 'json', 24 + url: '/foo', 25 + ...options 26 + }); 27 + };
+21
packages/openapi-ts-tests/test/__snapshots__/3.1.x/content-types/types.gen.ts
··· 1 + // This file is auto-generated by @hey-api/openapi-ts 2 + 3 + export type GetFooData = { 4 + body?: never; 5 + path?: never; 6 + query?: never; 7 + url: '/foo'; 8 + }; 9 + 10 + export type GetFooResponses = { 11 + /** 12 + * OK 13 + */ 14 + 200: string; 15 + }; 16 + 17 + export type GetFooResponse = GetFooResponses[keyof GetFooResponses]; 18 + 19 + export type ClientOptions = { 20 + baseURL: `${string}://${string}` | (string & {}); 21 + };
+2 -2
packages/openapi-ts-tests/test/openapi-ts.config.ts
··· 60 60 // 'invalid', 61 61 // 'servers-entry.yaml', 62 62 // ), 63 - path: path.resolve(__dirname, 'spec', '3.1.x', 'full.yaml'), 63 + path: path.resolve(__dirname, 'spec', '3.1.x', 'content-types.yaml'), 64 64 // path: path.resolve(__dirname, 'spec', 'v3-transforms.json'), 65 65 // path: 'http://localhost:4000/', 66 66 // path: 'https://get.heyapi.dev/', ··· 103 103 { 104 104 // baseUrl: false, 105 105 // exportFromIndex: true, 106 - // name: '@hey-api/client-fetch', 106 + name: '@hey-api/client-axios', 107 107 // name: 'legacy/angular', 108 108 // strictBaseUrl: true, 109 109 // throwOnError: true,
+20
packages/openapi-ts-tests/test/spec/3.0.x/content-types.yaml
··· 1 + openapi: 3.0.0 2 + info: 3 + title: OpenAPI 3.0.0 content types example 4 + version: '1' 5 + paths: 6 + /foo: 7 + get: 8 + responses: 9 + '200': 10 + description: OK 11 + content: 12 + text/plain: 13 + schema: 14 + type: string 15 + application/json: 16 + schema: 17 + type: string 18 + text/json: 19 + schema: 20 + type: string
+20
packages/openapi-ts-tests/test/spec/3.1.x/content-types.yaml
··· 1 + openapi: 3.1.0 2 + info: 3 + title: OpenAPI 3.1.0 content types example 4 + version: '1' 5 + paths: 6 + /foo: 7 + get: 8 + responses: 9 + '200': 10 + description: OK 11 + content: 12 + text/plain: 13 + schema: 14 + type: string 15 + application/json: 16 + schema: 17 + type: string 18 + text/json: 19 + schema: 20 + type: string
+7 -6
packages/openapi-ts/src/openApi/2.0.x/parser/mediaType.ts
··· 52 52 return schema; 53 53 }; 54 54 55 - export const mediaTypeObject = ({ 55 + export const mediaTypeObjects = ({ 56 56 mimeTypes, 57 57 response, 58 58 }: { 59 59 mimeTypes: ReadonlyArray<string> | undefined; 60 60 response: Pick<ResponseObject, 'schema'>; 61 - }): Content | undefined => { 62 - // return the first supported MIME type 61 + }): ReadonlyArray<Content> => { 62 + const objects: Array<Content> = []; 63 + 63 64 for (const mediaType of mimeTypes ?? []) { 64 - return { 65 + objects.push({ 65 66 mediaType, 66 67 schema: response.schema, 67 68 type: mediaTypeToIrMediaType({ mediaType }), 68 - }; 69 + }); 69 70 } 70 71 71 - return; 72 + return objects; 72 73 };
+9 -3
packages/openapi-ts/src/openApi/2.0.x/parser/operation.ts
··· 9 9 SchemaObject, 10 10 SecuritySchemeObject, 11 11 } from '../types/spec'; 12 - import { contentToSchema, mediaTypeObject } from './mediaType'; 12 + import { contentToSchema, mediaTypeObjects } from './mediaType'; 13 13 import { paginationField } from './pagination'; 14 14 import { schemaToIrSchema } from './schema'; 15 15 ··· 128 128 required: undefined, 129 129 type: requestBody.type === 'file' ? 'string' : requestBody.type, 130 130 }; 131 - const content = mediaTypeObject({ 131 + const contents = mediaTypeObjects({ 132 132 mimeTypes: operation.consumes, 133 133 response: { schema }, 134 134 }); 135 + // TODO: add support for multiple content types, for now prefer JSON 136 + const content = 137 + contents.find((content) => content.type === 'json') || contents[0]; 135 138 136 139 if (content) { 137 140 const pagination = paginationField({ ··· 224 227 '$ref' in response 225 228 ? context.resolveRef<ResponseObject>(response.$ref) 226 229 : response; 227 - const content = mediaTypeObject({ 230 + const contents = mediaTypeObjects({ 228 231 // assume JSON by default 229 232 mimeTypes: operation.produces ? operation.produces : ['application/json'], 230 233 response: responseObject, 231 234 }); 235 + // TODO: add support for multiple content types, for now prefer JSON 236 + const content = 237 + contents.find((content) => content.type === 'json') || contents[0]; 232 238 233 239 if (content) { 234 240 irOperation.responses[name] = {
+7 -6
packages/openapi-ts/src/openApi/3.0.x/parser/mediaType.ts
··· 52 52 return schema; 53 53 }; 54 54 55 - export const mediaTypeObject = ({ 55 + export const mediaTypeObjects = ({ 56 56 content, 57 57 }: { 58 58 content: Record<string, MediaTypeObject> | undefined; 59 - }): Content | undefined => { 60 - // return the first supported MIME type 59 + }): ReadonlyArray<Content> => { 60 + const objects: Array<Content> = []; 61 + 61 62 for (const mediaType in content) { 62 - return { 63 + objects.push({ 63 64 mediaType, 64 65 schema: content[mediaType]!.schema, 65 66 type: mediaTypeToIrMediaType({ mediaType }), 66 - }; 67 + }); 67 68 } 68 69 69 - return; 70 + return objects; 70 71 };
+10 -7
packages/openapi-ts/src/openApi/3.0.x/parser/operation.ts
··· 8 8 ResponseObject, 9 9 SecuritySchemeObject, 10 10 } from '../types/spec'; 11 - import { contentToSchema, mediaTypeObject } from './mediaType'; 11 + import { contentToSchema, mediaTypeObjects } from './mediaType'; 12 12 import { paginationField } from './pagination'; 13 13 import { schemaToIrSchema } from './schema'; 14 14 ··· 105 105 '$ref' in operation.requestBody 106 106 ? context.resolveRef<RequestBodyObject>(operation.requestBody.$ref) 107 107 : operation.requestBody; 108 - const content = mediaTypeObject({ 109 - content: requestBody.content, 110 - }); 108 + const contents = mediaTypeObjects({ content: requestBody.content }); 109 + // TODO: add support for multiple content types, for now prefer JSON 110 + const content = 111 + contents.find((content) => content.type === 'json') || contents[0]; 112 + 111 113 if (content) { 112 114 const pagination = paginationField({ 113 115 context, ··· 171 173 '$ref' in response 172 174 ? context.resolveRef<ResponseObject>(response.$ref) 173 175 : response; 174 - const content = mediaTypeObject({ 175 - content: responseObject.content, 176 - }); 176 + const contents = mediaTypeObjects({ content: responseObject.content }); 177 + // TODO: add support for multiple content types, for now prefer JSON 178 + const content = 179 + contents.find((content) => content.type === 'json') || contents[0]; 177 180 178 181 if (content) { 179 182 irOperation.responses[name] = {
+5 -2
packages/openapi-ts/src/openApi/3.0.x/parser/pagination.ts
··· 7 7 RequestBodyObject, 8 8 } from '../types/spec'; 9 9 import type { SchemaObject } from '../types/spec'; 10 - import { mediaTypeObject } from './mediaType'; 10 + import { mediaTypeObjects } from './mediaType'; 11 11 import { getSchemaType } from './schema'; 12 12 13 13 const isPaginationType = ( ··· 49 49 50 50 if (!refSchema) { 51 51 // parameter or body 52 - const content = mediaTypeObject({ content: ref.content }); 52 + const contents = mediaTypeObjects({ content: ref.content }); 53 + // TODO: add support for multiple content types, for now prefer JSON 54 + const content = 55 + contents.find((content) => content.type === 'json') || contents[0]; 53 56 if (content?.schema) { 54 57 refSchema = content.schema; 55 58 }
+5 -4
packages/openapi-ts/src/openApi/3.0.x/parser/parameter.ts
··· 5 5 ReferenceObject, 6 6 SchemaObject, 7 7 } from '../types/spec'; 8 - import { mediaTypeObject } from './mediaType'; 8 + import { mediaTypeObjects } from './mediaType'; 9 9 import { paginationField } from './pagination'; 10 10 import { schemaToIrSchema } from './schema'; 11 11 ··· 100 100 let schema = parameter.schema; 101 101 102 102 if (!schema) { 103 - const content = mediaTypeObject({ 104 - content: parameter.content, 105 - }); 103 + const contents = mediaTypeObjects({ content: parameter.content }); 104 + // TODO: add support for multiple content types, for now prefer JSON 105 + const content = 106 + contents.find((content) => content.type === 'json') || contents[0]; 106 107 if (content) { 107 108 schema = content.schema; 108 109 }
+5 -4
packages/openapi-ts/src/openApi/3.0.x/parser/requestBody.ts
··· 1 1 import type { IR } from '../../../ir/types'; 2 2 import { refToName } from '../../../utils/ref'; 3 3 import type { RequestBodyObject, SchemaObject } from '../types/spec'; 4 - import { mediaTypeObject } from './mediaType'; 4 + import { mediaTypeObjects } from './mediaType'; 5 5 import { schemaToIrSchema } from './schema'; 6 6 7 7 const requestBodyToIrRequestBody = ({ ··· 12 12 requestBody: RequestBodyObject; 13 13 }): IR.RequestBodyObject => { 14 14 // TODO: parser - fix 15 - const content = mediaTypeObject({ 16 - content: requestBody.content, 17 - }); 15 + const contents = mediaTypeObjects({ content: requestBody.content }); 16 + // TODO: add support for multiple content types, for now prefer JSON 17 + const content = 18 + contents.find((content) => content.type === 'json') || contents[0]; 18 19 const schema = content ? content.schema : undefined; 19 20 20 21 const finalSchema: SchemaObject = {
+7 -6
packages/openapi-ts/src/openApi/3.1.x/parser/mediaType.ts
··· 42 42 return schema; 43 43 }; 44 44 45 - export const mediaTypeObject = ({ 45 + export const mediaTypeObjects = ({ 46 46 content, 47 47 }: { 48 48 content: Record<string, MediaTypeObject> | undefined; 49 - }): Content | undefined => { 50 - // return the first supported MIME type 49 + }): ReadonlyArray<Content> => { 50 + const objects: Array<Content> = []; 51 + 51 52 for (const mediaType in content) { 52 - return { 53 + objects.push({ 53 54 mediaType, 54 55 schema: content[mediaType]!.schema, 55 56 type: mediaTypeToIrMediaType({ mediaType }), 56 - }; 57 + }); 57 58 } 58 59 59 - return; 60 + return objects; 60 61 };
+10 -7
packages/openapi-ts/src/openApi/3.1.x/parser/operation.ts
··· 8 8 ResponseObject, 9 9 SecuritySchemeObject, 10 10 } from '../types/spec'; 11 - import { contentToSchema, mediaTypeObject } from './mediaType'; 11 + import { contentToSchema, mediaTypeObjects } from './mediaType'; 12 12 import { paginationField } from './pagination'; 13 13 import { schemaToIrSchema } from './schema'; 14 14 ··· 105 105 '$ref' in operation.requestBody 106 106 ? context.resolveRef<RequestBodyObject>(operation.requestBody.$ref) 107 107 : operation.requestBody; 108 - const content = mediaTypeObject({ 109 - content: requestBody.content, 110 - }); 108 + const contents = mediaTypeObjects({ content: requestBody.content }); 109 + // TODO: add support for multiple content types, for now prefer JSON 110 + const content = 111 + contents.find((content) => content.type === 'json') || contents[0]; 112 + 111 113 if (content) { 112 114 const pagination = paginationField({ 113 115 context, ··· 156 158 '$ref' in response 157 159 ? context.resolveRef<ResponseObject>(response.$ref) 158 160 : response; 159 - const content = mediaTypeObject({ 160 - content: responseObject.content, 161 - }); 161 + const contents = mediaTypeObjects({ content: responseObject.content }); 162 + // TODO: add support for multiple content types, for now prefer JSON 163 + const content = 164 + contents.find((content) => content.type === 'json') || contents[0]; 162 165 163 166 if (content) { 164 167 irOperation.responses[name] = {
+5 -2
packages/openapi-ts/src/openApi/3.1.x/parser/pagination.ts
··· 3 3 import type { SchemaType } from '../../shared/types/schema'; 4 4 import type { ParameterObject, RequestBodyObject } from '../types/spec'; 5 5 import type { SchemaObject } from '../types/spec'; 6 - import { mediaTypeObject } from './mediaType'; 6 + import { mediaTypeObjects } from './mediaType'; 7 7 import { getSchemaTypes } from './schema'; 8 8 9 9 const isPaginationType = ( ··· 45 45 46 46 if (!refSchema) { 47 47 // parameter or body 48 - const content = mediaTypeObject({ content: ref.content }); 48 + const contents = mediaTypeObjects({ content: ref.content }); 49 + // TODO: add support for multiple content types, for now prefer JSON 50 + const content = 51 + contents.find((content) => content.type === 'json') || contents[0]; 49 52 if (content?.schema) { 50 53 refSchema = content.schema; 51 54 }
+5 -4
packages/openapi-ts/src/openApi/3.1.x/parser/parameter.ts
··· 5 5 ReferenceObject, 6 6 SchemaObject, 7 7 } from '../types/spec'; 8 - import { mediaTypeObject } from './mediaType'; 8 + import { mediaTypeObjects } from './mediaType'; 9 9 import { paginationField } from './pagination'; 10 10 import { schemaToIrSchema } from './schema'; 11 11 ··· 100 100 let schema = parameter.schema; 101 101 102 102 if (!schema) { 103 - const content = mediaTypeObject({ 104 - content: parameter.content, 105 - }); 103 + const contents = mediaTypeObjects({ content: parameter.content }); 104 + // TODO: add support for multiple content types, for now prefer JSON 105 + const content = 106 + contents.find((content) => content.type === 'json') || contents[0]; 106 107 if (content) { 107 108 schema = content.schema; 108 109 }
+5 -4
packages/openapi-ts/src/openApi/3.1.x/parser/requestBody.ts
··· 1 1 import type { IR } from '../../../ir/types'; 2 2 import { refToName } from '../../../utils/ref'; 3 3 import type { RequestBodyObject, SchemaObject } from '../types/spec'; 4 - import { mediaTypeObject } from './mediaType'; 4 + import { mediaTypeObjects } from './mediaType'; 5 5 import { schemaToIrSchema } from './schema'; 6 6 7 7 const requestBodyToIrRequestBody = ({ ··· 12 12 requestBody: RequestBodyObject; 13 13 }): IR.RequestBodyObject => { 14 14 // TODO: parser - fix 15 - const content = mediaTypeObject({ 16 - content: requestBody.content, 17 - }); 15 + const contents = mediaTypeObjects({ content: requestBody.content }); 16 + // TODO: add support for multiple content types, for now prefer JSON 17 + const content = 18 + contents.find((content) => content.type === 'json') || contents[0]; 18 19 const schema = content ? content.schema : undefined; 19 20 20 21 const finalSchema: SchemaObject = {