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.

at main 432 lines 10 kB view raw
1import { getAuthToken } from './core/auth'; 2import type { 3 QuerySerializer, 4 QuerySerializerOptions, 5} from './core/bodySerializer'; 6import { jsonBodySerializer } from './core/bodySerializer'; 7import { 8 serializeArrayParam, 9 serializeObjectParam, 10 serializePrimitiveParam, 11} from './core/pathSerializer'; 12import type { Client, ClientOptions, Config, RequestOptions } from './types'; 13 14interface PathSerializer { 15 path: Record<string, unknown>; 16 url: string; 17} 18 19const PATH_PARAM_RE = /\{[^{}]+\}/g; 20 21type ArrayStyle = 'form' | 'spaceDelimited' | 'pipeDelimited'; 22type MatrixStyle = 'label' | 'matrix' | 'simple'; 23type ArraySeparatorStyle = ArrayStyle | MatrixStyle; 24 25const defaultPathSerializer = ({ path, url: _url }: PathSerializer) => { 26 let url = _url; 27 const matches = _url.match(PATH_PARAM_RE); 28 if (matches) { 29 for (const match of matches) { 30 let explode = false; 31 let name = match.substring(1, match.length - 1); 32 let style: ArraySeparatorStyle = 'simple'; 33 34 if (name.endsWith('*')) { 35 explode = true; 36 name = name.substring(0, name.length - 1); 37 } 38 39 if (name.startsWith('.')) { 40 name = name.substring(1); 41 style = 'label'; 42 } else if (name.startsWith(';')) { 43 name = name.substring(1); 44 style = 'matrix'; 45 } 46 47 const value = path[name]; 48 49 if (value === undefined || value === null) { 50 continue; 51 } 52 53 if (Array.isArray(value)) { 54 url = url.replace( 55 match, 56 serializeArrayParam({ explode, name, style, value }), 57 ); 58 continue; 59 } 60 61 if (typeof value === 'object') { 62 url = url.replace( 63 match, 64 serializeObjectParam({ 65 explode, 66 name, 67 style, 68 value: value as Record<string, unknown>, 69 valueOnly: true, 70 }), 71 ); 72 continue; 73 } 74 75 if (style === 'matrix') { 76 url = url.replace( 77 match, 78 `;${serializePrimitiveParam({ 79 name, 80 value: value as string, 81 })}`, 82 ); 83 continue; 84 } 85 86 const replaceValue = encodeURIComponent( 87 style === 'label' ? `.${value as string}` : (value as string), 88 ); 89 url = url.replace(match, replaceValue); 90 } 91 } 92 return url; 93}; 94 95export const createQuerySerializer = <T = unknown>({ 96 allowReserved, 97 array, 98 object, 99}: QuerySerializerOptions = {}) => { 100 const querySerializer = (queryParams: T) => { 101 const search: string[] = []; 102 if (queryParams && typeof queryParams === 'object') { 103 for (const name in queryParams) { 104 const value = queryParams[name]; 105 106 if (value === undefined || value === null) { 107 continue; 108 } 109 110 if (Array.isArray(value)) { 111 const serializedArray = serializeArrayParam({ 112 allowReserved, 113 explode: true, 114 name, 115 style: 'form', 116 value, 117 ...array, 118 }); 119 if (serializedArray) search.push(serializedArray); 120 } else if (typeof value === 'object') { 121 const serializedObject = serializeObjectParam({ 122 allowReserved, 123 explode: true, 124 name, 125 style: 'deepObject', 126 value: value as Record<string, unknown>, 127 ...object, 128 }); 129 if (serializedObject) search.push(serializedObject); 130 } else { 131 const serializedPrimitive = serializePrimitiveParam({ 132 allowReserved, 133 name, 134 value: value as string, 135 }); 136 if (serializedPrimitive) search.push(serializedPrimitive); 137 } 138 } 139 } 140 return search.join('&'); 141 }; 142 return querySerializer; 143}; 144 145/** 146 * Infers parseAs value from provided Content-Type header. 147 */ 148export const getParseAs = ( 149 contentType: string | null, 150): Exclude<Config['parseAs'], 'auto'> => { 151 if (!contentType) { 152 // If no Content-Type header is provided, the best we can do is return the raw response body, 153 // which is effectively the same as the 'stream' option. 154 return 'stream'; 155 } 156 157 const cleanContent = contentType.split(';')[0]?.trim(); 158 159 if (!cleanContent) { 160 return; 161 } 162 163 if ( 164 cleanContent.startsWith('application/json') || 165 cleanContent.endsWith('+json') 166 ) { 167 return 'json'; 168 } 169 170 if (cleanContent === 'multipart/form-data') { 171 return 'formData'; 172 } 173 174 if ( 175 ['application/', 'audio/', 'image/', 'video/'].some((type) => 176 cleanContent.startsWith(type), 177 ) 178 ) { 179 return 'blob'; 180 } 181 182 if (cleanContent.startsWith('text/')) { 183 return 'text'; 184 } 185 186 return; 187}; 188 189const checkForExistence = ( 190 options: Pick<RequestOptions, 'auth' | 'query'> & { 191 headers: Headers; 192 }, 193 name?: string, 194): boolean => { 195 if (!name) { 196 return false; 197 } 198 if ( 199 options.headers.has(name) || 200 options.query?.[name] || 201 options.headers.get('Cookie')?.includes(`${name}=`) 202 ) { 203 return true; 204 } 205 return false; 206}; 207 208export const setAuthParams = async ({ 209 security, 210 ...options 211}: Pick<Required<RequestOptions>, 'security'> & 212 Pick<RequestOptions, 'auth' | 'query'> & { 213 headers: Headers; 214 }) => { 215 for (const auth of security) { 216 if (checkForExistence(options, auth.name)) { 217 continue; 218 } 219 220 const token = await getAuthToken(auth, options.auth); 221 222 if (!token) { 223 continue; 224 } 225 226 const name = auth.name ?? 'Authorization'; 227 228 switch (auth.in) { 229 case 'query': 230 if (!options.query) { 231 options.query = {}; 232 } 233 options.query[name] = token; 234 break; 235 case 'cookie': 236 options.headers.append('Cookie', `${name}=${token}`); 237 break; 238 case 'header': 239 default: 240 options.headers.set(name, token); 241 break; 242 } 243 } 244}; 245 246export const buildUrl: Client['buildUrl'] = (options) => { 247 const url = getUrl({ 248 baseUrl: options.baseUrl as string, 249 path: options.path, 250 query: options.query, 251 querySerializer: 252 typeof options.querySerializer === 'function' 253 ? options.querySerializer 254 : createQuerySerializer(options.querySerializer), 255 url: options.url, 256 }); 257 return url; 258}; 259 260export const getUrl = ({ 261 baseUrl, 262 path, 263 query, 264 querySerializer, 265 url: _url, 266}: { 267 baseUrl?: string; 268 path?: Record<string, unknown>; 269 query?: Record<string, unknown>; 270 querySerializer: QuerySerializer; 271 url: string; 272}) => { 273 const pathUrl = _url.startsWith('/') ? _url : `/${_url}`; 274 let url = (baseUrl ?? '') + pathUrl; 275 if (path) { 276 url = defaultPathSerializer({ path, url }); 277 } 278 let search = query ? querySerializer(query) : ''; 279 if (search.startsWith('?')) { 280 search = search.substring(1); 281 } 282 if (search) { 283 url += `?${search}`; 284 } 285 return url; 286}; 287 288export const mergeConfigs = (a: Config, b: Config): Config => { 289 const config = { ...a, ...b }; 290 if (config.baseUrl?.endsWith('/')) { 291 config.baseUrl = config.baseUrl.substring(0, config.baseUrl.length - 1); 292 } 293 config.headers = mergeHeaders(a.headers, b.headers); 294 return config; 295}; 296 297export const mergeHeaders = ( 298 ...headers: Array<Required<Config>['headers'] | undefined> 299): Headers => { 300 const mergedHeaders = new Headers(); 301 for (const header of headers) { 302 if (!header || typeof header !== 'object') { 303 continue; 304 } 305 306 const iterator = 307 header instanceof Headers ? header.entries() : Object.entries(header); 308 309 for (const [key, value] of iterator) { 310 if (value === null) { 311 mergedHeaders.delete(key); 312 } else if (Array.isArray(value)) { 313 for (const v of value) { 314 mergedHeaders.append(key, v as string); 315 } 316 } else if (value !== undefined) { 317 // assume object headers are meant to be JSON stringified, i.e. their 318 // content value in OpenAPI specification is 'application/json' 319 mergedHeaders.set( 320 key, 321 typeof value === 'object' ? JSON.stringify(value) : (value as string), 322 ); 323 } 324 } 325 } 326 return mergedHeaders; 327}; 328 329type ErrInterceptor<Err, Res, Req, Options> = ( 330 error: Err, 331 response: Res, 332 request: Req, 333 options: Options, 334) => Err | Promise<Err>; 335 336type ReqInterceptor<Req, Options> = ( 337 request: Req, 338 options: Options, 339) => Req | Promise<Req>; 340 341type ResInterceptor<Res, Req, Options> = ( 342 response: Res, 343 request: Req, 344 options: Options, 345) => Res | Promise<Res>; 346 347class Interceptors<Interceptor> { 348 fns: Array<Interceptor | null> = []; 349 350 clear(): void { 351 this.fns = []; 352 } 353 354 eject(id: number | Interceptor): void { 355 const index = this.getInterceptorIndex(id); 356 if (this.fns[index]) { 357 this.fns[index] = null; 358 } 359 } 360 361 exists(id: number | Interceptor): boolean { 362 const index = this.getInterceptorIndex(id); 363 return Boolean(this.fns[index]); 364 } 365 366 getInterceptorIndex(id: number | Interceptor): number { 367 if (typeof id === 'number') { 368 return this.fns[id] ? id : -1; 369 } 370 return this.fns.indexOf(id); 371 } 372 373 update( 374 id: number | Interceptor, 375 fn: Interceptor, 376 ): number | Interceptor | false { 377 const index = this.getInterceptorIndex(id); 378 if (this.fns[index]) { 379 this.fns[index] = fn; 380 return id; 381 } 382 return false; 383 } 384 385 use(fn: Interceptor): number { 386 this.fns.push(fn); 387 return this.fns.length - 1; 388 } 389} 390 391export interface Middleware<Req, Res, Err, Options> { 392 error: Interceptors<ErrInterceptor<Err, Res, Req, Options>>; 393 request: Interceptors<ReqInterceptor<Req, Options>>; 394 response: Interceptors<ResInterceptor<Res, Req, Options>>; 395} 396 397export const createInterceptors = <Req, Res, Err, Options>(): Middleware< 398 Req, 399 Res, 400 Err, 401 Options 402> => ({ 403 error: new Interceptors<ErrInterceptor<Err, Res, Req, Options>>(), 404 request: new Interceptors<ReqInterceptor<Req, Options>>(), 405 response: new Interceptors<ResInterceptor<Res, Req, Options>>(), 406}); 407 408const defaultQuerySerializer = createQuerySerializer({ 409 allowReserved: false, 410 array: { 411 explode: true, 412 style: 'form', 413 }, 414 object: { 415 explode: true, 416 style: 'deepObject', 417 }, 418}); 419 420const defaultHeaders = { 421 'Content-Type': 'application/json', 422}; 423 424export const createConfig = <T extends ClientOptions = ClientOptions>( 425 override: Config<Omit<ClientOptions, keyof T> & T> = {}, 426): Config<Omit<ClientOptions, keyof T> & T> => ({ 427 ...jsonBodySerializer, 428 headers: defaultHeaders, 429 parseAs: 'auto', 430 querySerializer: defaultQuerySerializer, 431 ...override, 432});