From df5c69048a03a1c7729a5200c586164287a8a6fa Mon Sep 17 00:00:00 2001 From: Lubos Date: Fri, 27 Sep 2024 20:50:19 +0800 Subject: [PATCH] feat: add error interceptors to fetch client --- .changeset/old-spoons-listen.md | 5 ++++ .changeset/small-goats-look.md | 5 ++++ examples/openapi-ts-fetch/src/App.tsx | 5 ++++ packages/client-fetch/src/index.ts | 26 ++++++++++++++----- packages/client-fetch/src/types.ts | 3 ++- packages/client-fetch/src/utils.ts | 16 ++++++++++-- .../client/index.ts.snap | 26 ++++++++++++++----- .../client/types.ts.snap | 3 ++- .../client/utils.ts.snap | 16 ++++++++++-- .../client/index.ts.snap | 26 ++++++++++++++----- .../client/types.ts.snap | 3 ++- .../client/utils.ts.snap | 16 ++++++++++-- 12 files changed, 123 insertions(+), 27 deletions(-) create mode 100644 .changeset/old-spoons-listen.md create mode 100644 .changeset/small-goats-look.md diff --git a/.changeset/old-spoons-listen.md b/.changeset/old-spoons-listen.md new file mode 100644 index 000000000..3fc433aae --- /dev/null +++ b/.changeset/old-spoons-listen.md @@ -0,0 +1,5 @@ +--- +'@hey-api/client-fetch': minor +--- + +feat: add error interceptors diff --git a/.changeset/small-goats-look.md b/.changeset/small-goats-look.md new file mode 100644 index 000000000..b84e5b17b --- /dev/null +++ b/.changeset/small-goats-look.md @@ -0,0 +1,5 @@ +--- +'@hey-api/client-fetch': patch +--- + +fix: throw raw error when throwOnError is true diff --git a/examples/openapi-ts-fetch/src/App.tsx b/examples/openapi-ts-fetch/src/App.tsx index 03c848868..4e5f079cb 100644 --- a/examples/openapi-ts-fetch/src/App.tsx +++ b/examples/openapi-ts-fetch/src/App.tsx @@ -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(); const [isRequiredNameError, setIsRequiredNameError] = useState(false); diff --git a/packages/client-fetch/src/index.ts b/packages/client-fetch/src/index.ts index 29cc066a6..9e8f86945 100644 --- a/packages/client-fetch/src/index.ts +++ b/packages/client-fetch/src/index.ts @@ -24,7 +24,12 @@ export const createClient = (config: Config = {}): Client => { return getConfig(); }; - const interceptors = createInterceptors(); + const interceptors = createInterceptors< + Request, + Response, + unknown, + RequestOptions + >(); // @ts-expect-error const request: Client['request'] = async (options) => { @@ -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, }; }; diff --git a/packages/client-fetch/src/types.ts b/packages/client-fetch/src/types.ts index d53ca45da..1a3eb3ebc 100644 --- a/packages/client-fetch/src/types.ts +++ b/packages/client-fetch/src/types.ts @@ -145,6 +145,7 @@ type RequestFn = < export interface Client< Req = Request, Res = Response, + Err = unknown, Options = RequestOptions, > { connect: MethodFn; @@ -152,7 +153,7 @@ export interface Client< get: MethodFn; getConfig: () => Config; head: MethodFn; - interceptors: Middleware; + interceptors: Middleware; options: MethodFn; patch: MethodFn; post: MethodFn; diff --git a/packages/client-fetch/src/utils.ts b/packages/client-fetch/src/utils.ts index 2bcc2e5e5..8e5185670 100644 --- a/packages/client-fetch/src/utils.ts +++ b/packages/client-fetch/src/utils.ts @@ -422,6 +422,13 @@ export const mergeHeaders = ( return mergedHeaders; }; +type ErrInterceptor = ( + error: Err, + response: Res, + request: Req, + options: Options, +) => Err | Promise; + type ReqInterceptor = ( request: Req, options: Options, @@ -462,7 +469,11 @@ class Interceptors { // `createInterceptors()` response, meant for external use as it does not // expose internals -export interface Middleware { +export interface Middleware { + error: Pick< + Interceptors>, + 'eject' | 'use' + >; request: Pick>, 'eject' | 'use'>; response: Pick< Interceptors>, @@ -471,7 +482,8 @@ export interface Middleware { } // do not add `Middleware` as return type so we can use _fns internally -export const createInterceptors = () => ({ +export const createInterceptors = () => ({ + error: new Interceptors>(), request: new Interceptors>(), response: new Interceptors>(), }); diff --git a/packages/openapi-ts/test/__snapshots__/test/generated/v3-hey-api-client-fetch-bundle/client/index.ts.snap b/packages/openapi-ts/test/__snapshots__/test/generated/v3-hey-api-client-fetch-bundle/client/index.ts.snap index 29cc066a6..9e8f86945 100644 --- a/packages/openapi-ts/test/__snapshots__/test/generated/v3-hey-api-client-fetch-bundle/client/index.ts.snap +++ b/packages/openapi-ts/test/__snapshots__/test/generated/v3-hey-api-client-fetch-bundle/client/index.ts.snap @@ -24,7 +24,12 @@ export const createClient = (config: Config = {}): Client => { return getConfig(); }; - const interceptors = createInterceptors(); + const interceptors = createInterceptors< + Request, + Response, + unknown, + RequestOptions + >(); // @ts-expect-error const request: Client['request'] = async (options) => { @@ -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, }; }; diff --git a/packages/openapi-ts/test/__snapshots__/test/generated/v3-hey-api-client-fetch-bundle/client/types.ts.snap b/packages/openapi-ts/test/__snapshots__/test/generated/v3-hey-api-client-fetch-bundle/client/types.ts.snap index d53ca45da..1a3eb3ebc 100644 --- a/packages/openapi-ts/test/__snapshots__/test/generated/v3-hey-api-client-fetch-bundle/client/types.ts.snap +++ b/packages/openapi-ts/test/__snapshots__/test/generated/v3-hey-api-client-fetch-bundle/client/types.ts.snap @@ -145,6 +145,7 @@ type RequestFn = < export interface Client< Req = Request, Res = Response, + Err = unknown, Options = RequestOptions, > { connect: MethodFn; @@ -152,7 +153,7 @@ export interface Client< get: MethodFn; getConfig: () => Config; head: MethodFn; - interceptors: Middleware; + interceptors: Middleware; options: MethodFn; patch: MethodFn; post: MethodFn; diff --git a/packages/openapi-ts/test/__snapshots__/test/generated/v3-hey-api-client-fetch-bundle/client/utils.ts.snap b/packages/openapi-ts/test/__snapshots__/test/generated/v3-hey-api-client-fetch-bundle/client/utils.ts.snap index 2bcc2e5e5..8e5185670 100644 --- a/packages/openapi-ts/test/__snapshots__/test/generated/v3-hey-api-client-fetch-bundle/client/utils.ts.snap +++ b/packages/openapi-ts/test/__snapshots__/test/generated/v3-hey-api-client-fetch-bundle/client/utils.ts.snap @@ -422,6 +422,13 @@ export const mergeHeaders = ( return mergedHeaders; }; +type ErrInterceptor = ( + error: Err, + response: Res, + request: Req, + options: Options, +) => Err | Promise; + type ReqInterceptor = ( request: Req, options: Options, @@ -462,7 +469,11 @@ class Interceptors { // `createInterceptors()` response, meant for external use as it does not // expose internals -export interface Middleware { +export interface Middleware { + error: Pick< + Interceptors>, + 'eject' | 'use' + >; request: Pick>, 'eject' | 'use'>; response: Pick< Interceptors>, @@ -471,7 +482,8 @@ export interface Middleware { } // do not add `Middleware` as return type so we can use _fns internally -export const createInterceptors = () => ({ +export const createInterceptors = () => ({ + error: new Interceptors>(), request: new Interceptors>(), response: new Interceptors>(), }); diff --git a/packages/openapi-ts/test/__snapshots__/test/generated/v3-hey-api-client-fetch-bundle_transform/client/index.ts.snap b/packages/openapi-ts/test/__snapshots__/test/generated/v3-hey-api-client-fetch-bundle_transform/client/index.ts.snap index 29cc066a6..9e8f86945 100644 --- a/packages/openapi-ts/test/__snapshots__/test/generated/v3-hey-api-client-fetch-bundle_transform/client/index.ts.snap +++ b/packages/openapi-ts/test/__snapshots__/test/generated/v3-hey-api-client-fetch-bundle_transform/client/index.ts.snap @@ -24,7 +24,12 @@ export const createClient = (config: Config = {}): Client => { return getConfig(); }; - const interceptors = createInterceptors(); + const interceptors = createInterceptors< + Request, + Response, + unknown, + RequestOptions + >(); // @ts-expect-error const request: Client['request'] = async (options) => { @@ -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, }; }; diff --git a/packages/openapi-ts/test/__snapshots__/test/generated/v3-hey-api-client-fetch-bundle_transform/client/types.ts.snap b/packages/openapi-ts/test/__snapshots__/test/generated/v3-hey-api-client-fetch-bundle_transform/client/types.ts.snap index d53ca45da..1a3eb3ebc 100644 --- a/packages/openapi-ts/test/__snapshots__/test/generated/v3-hey-api-client-fetch-bundle_transform/client/types.ts.snap +++ b/packages/openapi-ts/test/__snapshots__/test/generated/v3-hey-api-client-fetch-bundle_transform/client/types.ts.snap @@ -145,6 +145,7 @@ type RequestFn = < export interface Client< Req = Request, Res = Response, + Err = unknown, Options = RequestOptions, > { connect: MethodFn; @@ -152,7 +153,7 @@ export interface Client< get: MethodFn; getConfig: () => Config; head: MethodFn; - interceptors: Middleware; + interceptors: Middleware; options: MethodFn; patch: MethodFn; post: MethodFn; diff --git a/packages/openapi-ts/test/__snapshots__/test/generated/v3-hey-api-client-fetch-bundle_transform/client/utils.ts.snap b/packages/openapi-ts/test/__snapshots__/test/generated/v3-hey-api-client-fetch-bundle_transform/client/utils.ts.snap index 2bcc2e5e5..8e5185670 100644 --- a/packages/openapi-ts/test/__snapshots__/test/generated/v3-hey-api-client-fetch-bundle_transform/client/utils.ts.snap +++ b/packages/openapi-ts/test/__snapshots__/test/generated/v3-hey-api-client-fetch-bundle_transform/client/utils.ts.snap @@ -422,6 +422,13 @@ export const mergeHeaders = ( return mergedHeaders; }; +type ErrInterceptor = ( + error: Err, + response: Res, + request: Req, + options: Options, +) => Err | Promise; + type ReqInterceptor = ( request: Req, options: Options, @@ -462,7 +469,11 @@ class Interceptors { // `createInterceptors()` response, meant for external use as it does not // expose internals -export interface Middleware { +export interface Middleware { + error: Pick< + Interceptors>, + 'eject' | 'use' + >; request: Pick>, 'eject' | 'use'>; response: Pick< Interceptors>, @@ -471,7 +482,8 @@ export interface Middleware { } // do not add `Middleware` as return type so we can use _fns internally -export const createInterceptors = () => ({ +export const createInterceptors = () => ({ + error: new Interceptors>(), request: new Interceptors>(), response: new Interceptors>(), });