@kubb/plugin-axios 5.0.0-beta.75 → 5.0.0-beta.76

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -86,7 +86,8 @@ export type BodySerializer = (body: unknown, contentType?: string) => unknown
86
86
 
87
87
  /**
88
88
  * Parses a value before it is sent or after it is received, returning the parsed (and optionally
89
- * transformed) value. Wires zod parsing through the per-call `parser.request` / `parser.response` hooks.
89
+ * transformed) value. Wires zod parsing through the per-call `parser.request` / `parser.response` /
90
+ * `parser.error` hooks (`error` runs on the error body when a non-2xx call does not throw).
90
91
  */
91
92
  export type Parser<T = unknown> = (value: T) => T | Promise<T>
92
93
 
@@ -136,7 +137,7 @@ export type RequestConfig<TBody = unknown, TRequest = AxiosRequestConfig, TRespo
136
137
  transport?: AxiosInstance
137
138
  querySerializer?: QuerySerializer
138
139
  bodySerializer?: BodySerializer
139
- parser?: { request?: Parser; response?: Parser }
140
+ parser?: { request?: Parser; response?: Parser; error?: Parser }
140
141
  security?: Array<Auth>
141
142
  auth?: AuthResolver
142
143
  }
@@ -254,13 +255,30 @@ function isFormBody(body: unknown): boolean {
254
255
  )
255
256
  }
256
257
 
258
+ function appendFormDataValue(formData: FormData, key: string, value: unknown): void {
259
+ if (value === undefined || value === null) return
260
+ if (value instanceof Blob) formData.append(key, value)
261
+ else if (value instanceof Date) formData.append(key, value.toISOString())
262
+ else if (typeof value === 'object') formData.append(key, JSON.stringify(value))
263
+ else formData.append(key, String(value))
264
+ }
265
+
257
266
  /**
258
267
  * Default body serializer: passes binary/form bodies through and JSON-serializes everything else.
259
- * For `application/x-www-form-urlencoded` plain objects become `URLSearchParams`.
268
+ * For `multipart/form-data` plain objects become `FormData` and for
269
+ * `application/x-www-form-urlencoded` they become `URLSearchParams`.
260
270
  */
261
271
  export const defaultBodySerializer: BodySerializer = (body, contentType) => {
262
272
  if (body === undefined || body === null) return undefined
263
273
  if (isFormBody(body)) return body
274
+ if (contentType?.includes('multipart/form-data')) {
275
+ const formData = new FormData()
276
+ for (const [key, value] of Object.entries(body as Record<string, unknown>)) {
277
+ if (Array.isArray(value)) for (const item of value) appendFormDataValue(formData, key, item)
278
+ else appendFormDataValue(formData, key, value)
279
+ }
280
+ return formData
281
+ }
264
282
  if (contentType?.includes('application/x-www-form-urlencoded')) {
265
283
  return new URLSearchParams(body as Record<string, string>)
266
284
  }
@@ -426,10 +444,7 @@ export function createClientCore<TRequest = AxiosRequestConfig, TResponse = Axio
426
444
  const bodySerializer = requestConfig.bodySerializer ?? config.bodySerializer ?? defaultBodySerializer
427
445
 
428
446
  const headers = mergeHeaders(config.headers, requestConfig.headers)
429
- if (requestConfig.contentType && requestConfig.contentType !== 'multipart/form-data') {
430
- headers['Content-Type'] = requestConfig.contentType
431
- }
432
- const contentType = headers['Content-Type'] ?? headers['content-type']
447
+ const requestContentType = requestConfig.contentType ?? headers['Content-Type'] ?? headers['content-type']
433
448
 
434
449
  const query: Record<string, unknown> = { ...((requestConfig.query ?? requestConfig.params) as Record<string, unknown> | undefined) }
435
450
 
@@ -441,6 +456,14 @@ export function createClientCore<TRequest = AxiosRequestConfig, TResponse = Axio
441
456
  })
442
457
 
443
458
  const validatedBody = await runParser(requestConfig.parser?.request, requestConfig.body)
459
+ const body = bodySerializer(validatedBody, requestContentType)
460
+ // A FormData body must keep its Content-Type unset so axios appends the multipart boundary.
461
+ if (body instanceof FormData) {
462
+ delete headers['Content-Type']
463
+ delete headers['content-type']
464
+ } else if (requestConfig.contentType) {
465
+ headers['Content-Type'] = requestConfig.contentType
466
+ }
444
467
  const pathParams = requestConfig.path ?? {}
445
468
  const url = (requestConfig.url ?? '').replace(/\{([^{}]+)\}/g, (_, key: string) => encodeURIComponent(String(pathParams[key] ?? '')))
446
469
  const baseURL = [config.baseURL, requestConfig.baseURL].filter(Boolean).join('') || undefined
@@ -455,8 +478,8 @@ export function createClientCore<TRequest = AxiosRequestConfig, TResponse = Axio
455
478
  headers,
456
479
  params: query,
457
480
  paramsSerializer: (params) => querySerializer(params as Record<string, unknown>),
458
- data: validatedBody,
459
- transformRequest: (data) => bodySerializer(data, contentType),
481
+ data: body,
482
+ transformRequest: (data) => data,
460
483
  signal: requestConfig.signal,
461
484
  responseType: requestConfig.responseType,
462
485
  validateStatus,
@@ -466,10 +489,11 @@ export function createClientCore<TRequest = AxiosRequestConfig, TResponse = Axio
466
489
  const response = await activeInstance.request<unknown, AxiosResponse>(axiosConfig)
467
490
  const isSuccess = response.status >= 200 && response.status < 300
468
491
  const data = isSuccess ? await runParser(requestConfig.parser?.response, response.data) : undefined
492
+ const error = isSuccess ? undefined : await runParser(requestConfig.parser?.error, response.data)
469
493
  return {
470
494
  status: response.status,
471
495
  data,
472
- error: isSuccess ? undefined : response.data,
496
+ error,
473
497
  request: response.config as TRequest,
474
498
  response: response as TResponse,
475
499
  }