Skip to content

Commit

Permalink
feat: add error interceptors to fetch client
Browse files Browse the repository at this point in the history
  • Loading branch information
mrlubos committed Sep 27, 2024
1 parent 9c4b005 commit df5c690
Show file tree
Hide file tree
Showing 12 changed files with 123 additions and 27 deletions.
5 changes: 5 additions & 0 deletions .changeset/old-spoons-listen.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@hey-api/client-fetch': minor
---

feat: add error interceptors
5 changes: 5 additions & 0 deletions .changeset/small-goats-look.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@hey-api/client-fetch': patch
---

fix: throw raw error when throwOnError is true
5 changes: 5 additions & 0 deletions examples/openapi-ts-fetch/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,11 @@ localClient.interceptors.request.use((request, options) => {
return request;
});

localClient.interceptors.error.use((error) => {
console.log(error);
return error;
});

function App() {
const [pet, setPet] = useState<Pet>();
const [isRequiredNameError, setIsRequiredNameError] = useState(false);
Expand Down
26 changes: 20 additions & 6 deletions packages/client-fetch/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,12 @@ export const createClient = (config: Config = {}): Client => {
return getConfig();
};

const interceptors = createInterceptors<Request, Response, RequestOptions>();
const interceptors = createInterceptors<
Request,
Response,
unknown,
RequestOptions
>();

// @ts-expect-error
const request: Client['request'] = async (options) => {
Expand Down Expand Up @@ -113,17 +118,26 @@ export const createClient = (config: Config = {}): Client => {

let error = await response.text();

if (opts.throwOnError) {
throw new Error(error);
}

try {
error = JSON.parse(error);
} catch {
// noop
}

let finalError = error;

for (const fn of interceptors.error._fns) {
finalError = (await fn(error, response, request, opts)) as string;
}

finalError = finalError || ({} as string);

if (opts.throwOnError) {
throw finalError;
}

return {
error: error || {},
error: finalError,
...result,
};
};
Expand Down
3 changes: 2 additions & 1 deletion packages/client-fetch/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,14 +145,15 @@ type RequestFn = <
export interface Client<
Req = Request,
Res = Response,
Err = unknown,
Options = RequestOptions,
> {
connect: MethodFn;
delete: MethodFn;
get: MethodFn;
getConfig: () => Config;
head: MethodFn;
interceptors: Middleware<Req, Res, Options>;
interceptors: Middleware<Req, Res, Err, Options>;
options: MethodFn;
patch: MethodFn;
post: MethodFn;
Expand Down
16 changes: 14 additions & 2 deletions packages/client-fetch/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -422,6 +422,13 @@ export const mergeHeaders = (
return mergedHeaders;
};

type ErrInterceptor<Err, Res, Req, Options> = (
error: Err,
response: Res,
request: Req,
options: Options,
) => Err | Promise<Err>;

type ReqInterceptor<Req, Options> = (
request: Req,
options: Options,
Expand Down Expand Up @@ -462,7 +469,11 @@ class Interceptors<Interceptor> {

// `createInterceptors()` response, meant for external use as it does not
// expose internals
export interface Middleware<Req, Res, Options> {
export interface Middleware<Req, Res, Err, Options> {
error: Pick<
Interceptors<ErrInterceptor<Err, Res, Req, Options>>,
'eject' | 'use'
>;
request: Pick<Interceptors<ReqInterceptor<Req, Options>>, 'eject' | 'use'>;
response: Pick<
Interceptors<ResInterceptor<Res, Req, Options>>,
Expand All @@ -471,7 +482,8 @@ export interface Middleware<Req, Res, Options> {
}

// do not add `Middleware` as return type so we can use _fns internally
export const createInterceptors = <Req, Res, Options>() => ({
export const createInterceptors = <Req, Res, Err, Options>() => ({
error: new Interceptors<ErrInterceptor<Err, Res, Req, Options>>(),
request: new Interceptors<ReqInterceptor<Req, Options>>(),
response: new Interceptors<ResInterceptor<Res, Req, Options>>(),
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,12 @@ export const createClient = (config: Config = {}): Client => {
return getConfig();
};

const interceptors = createInterceptors<Request, Response, RequestOptions>();
const interceptors = createInterceptors<
Request,
Response,
unknown,
RequestOptions
>();

// @ts-expect-error
const request: Client['request'] = async (options) => {
Expand Down Expand Up @@ -113,17 +118,26 @@ export const createClient = (config: Config = {}): Client => {

let error = await response.text();

if (opts.throwOnError) {
throw new Error(error);
}

try {
error = JSON.parse(error);
} catch {
// noop
}

let finalError = error;

for (const fn of interceptors.error._fns) {
finalError = (await fn(error, response, request, opts)) as string;
}

finalError = finalError || ({} as string);

if (opts.throwOnError) {
throw finalError;
}

return {
error: error || {},
error: finalError,
...result,
};
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -145,14 +145,15 @@ type RequestFn = <
export interface Client<
Req = Request,
Res = Response,
Err = unknown,
Options = RequestOptions,
> {
connect: MethodFn;
delete: MethodFn;
get: MethodFn;
getConfig: () => Config;
head: MethodFn;
interceptors: Middleware<Req, Res, Options>;
interceptors: Middleware<Req, Res, Err, Options>;
options: MethodFn;
patch: MethodFn;
post: MethodFn;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -422,6 +422,13 @@ export const mergeHeaders = (
return mergedHeaders;
};
type ErrInterceptor<Err, Res, Req, Options> = (
error: Err,
response: Res,
request: Req,
options: Options,
) => Err | Promise<Err>;
type ReqInterceptor<Req, Options> = (
request: Req,
options: Options,
Expand Down Expand Up @@ -462,7 +469,11 @@ class Interceptors<Interceptor> {
// `createInterceptors()` response, meant for external use as it does not
// expose internals
export interface Middleware<Req, Res, Options> {
export interface Middleware<Req, Res, Err, Options> {
error: Pick<
Interceptors<ErrInterceptor<Err, Res, Req, Options>>,
'eject' | 'use'
>;
request: Pick<Interceptors<ReqInterceptor<Req, Options>>, 'eject' | 'use'>;
response: Pick<
Interceptors<ResInterceptor<Res, Req, Options>>,
Expand All @@ -471,7 +482,8 @@ export interface Middleware<Req, Res, Options> {
}
// do not add `Middleware` as return type so we can use _fns internally
export const createInterceptors = <Req, Res, Options>() => ({
export const createInterceptors = <Req, Res, Err, Options>() => ({
error: new Interceptors<ErrInterceptor<Err, Res, Req, Options>>(),
request: new Interceptors<ReqInterceptor<Req, Options>>(),
response: new Interceptors<ResInterceptor<Res, Req, Options>>(),
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,12 @@ export const createClient = (config: Config = {}): Client => {
return getConfig();
};

const interceptors = createInterceptors<Request, Response, RequestOptions>();
const interceptors = createInterceptors<
Request,
Response,
unknown,
RequestOptions
>();

// @ts-expect-error
const request: Client['request'] = async (options) => {
Expand Down Expand Up @@ -113,17 +118,26 @@ export const createClient = (config: Config = {}): Client => {

let error = await response.text();

if (opts.throwOnError) {
throw new Error(error);
}

try {
error = JSON.parse(error);
} catch {
// noop
}

let finalError = error;

for (const fn of interceptors.error._fns) {
finalError = (await fn(error, response, request, opts)) as string;
}

finalError = finalError || ({} as string);

if (opts.throwOnError) {
throw finalError;
}

return {
error: error || {},
error: finalError,
...result,
};
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -145,14 +145,15 @@ type RequestFn = <
export interface Client<
Req = Request,
Res = Response,
Err = unknown,
Options = RequestOptions,
> {
connect: MethodFn;
delete: MethodFn;
get: MethodFn;
getConfig: () => Config;
head: MethodFn;
interceptors: Middleware<Req, Res, Options>;
interceptors: Middleware<Req, Res, Err, Options>;
options: MethodFn;
patch: MethodFn;
post: MethodFn;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -422,6 +422,13 @@ export const mergeHeaders = (
return mergedHeaders;
};
type ErrInterceptor<Err, Res, Req, Options> = (
error: Err,
response: Res,
request: Req,
options: Options,
) => Err | Promise<Err>;
type ReqInterceptor<Req, Options> = (
request: Req,
options: Options,
Expand Down Expand Up @@ -462,7 +469,11 @@ class Interceptors<Interceptor> {
// `createInterceptors()` response, meant for external use as it does not
// expose internals
export interface Middleware<Req, Res, Options> {
export interface Middleware<Req, Res, Err, Options> {
error: Pick<
Interceptors<ErrInterceptor<Err, Res, Req, Options>>,
'eject' | 'use'
>;
request: Pick<Interceptors<ReqInterceptor<Req, Options>>, 'eject' | 'use'>;
response: Pick<
Interceptors<ResInterceptor<Res, Req, Options>>,
Expand All @@ -471,7 +482,8 @@ export interface Middleware<Req, Res, Options> {
}
// do not add `Middleware` as return type so we can use _fns internally
export const createInterceptors = <Req, Res, Options>() => ({
export const createInterceptors = <Req, Res, Err, Options>() => ({
error: new Interceptors<ErrInterceptor<Err, Res, Req, Options>>(),
request: new Interceptors<ReqInterceptor<Req, Options>>(),
response: new Interceptors<ResInterceptor<Res, Req, Options>>(),
});
Expand Down

0 comments on commit df5c690

Please sign in to comment.