I have developed a streamlined Axios wrapper function that integrates zod-parsing and presents a discriminated union for improved error handling. While the implementation successfully maintains the default behavior of Axios to throw errors in certain cases by passing { throw: true }
to the config
parameter through overloading, I find the current setup verbose. Is there a way to refactor the overloading to reduce redundancy and enhance the DRY principle? My goal is to declare the generics Schema
and Parsed
just once instead of repeating them three times.
import type { AxiosRequestConfig } from 'axios';
import * as z from 'zod';
import * as URLS from '~/constants/api-url-constants';
import { coerceError } from '~/utils';
type Result<T> = { ok: true; response: T } | { ok: false; error: Error };
type Url = string | ((urls: typeof URLS) => string);
interface Config<Schema extends z.Schema, Throw extends boolean>
extends AxiosRequestConfig {
schema?: Schema | ((zod: typeof z) => Schema);
throw?: Catch;
}
/**
* This Axios wrapper function accepts Zod-schema to parse response data,
* providing typed response inferred from the schema's output type.
* Schema validations occur within `config.transformResponse`,
* allowing detection of failed Zod parsing.
*
* By default, it returns a discriminated union for error handling.
* Enable overload `config.catch` to mimic native Axios behavior and throw unsuccessful API calls.
*/
async function axiosWrapper<
Schema extends z.Schema = z.ZodUnknown,
Parsed = z.output<Schema>,
>(url: Url, config: Config<Schema, false>): Promise<Result<Parsed>>;
async function axiosWrapper<
Schema extends z.Schema = z.ZodUnknown,
Parsed = z.output<Schema>,
>(url: Url, config: Config<Schema, true>): Promise<Parsed>;
async function axiosWrapper<
Schema extends z.Schema = z.ZodUnknown,
Parsed = z.output<Schema>,
>(url: Url, config: Config<Schema, boolean>): Promise<Result<Parsed> | Parsed> {
// …implementation
}