Skip to content

Commit 6430af6

Browse files
committed
catchSchemaFailure idea
1 parent 566ea45 commit 6430af6

File tree

4 files changed

+84
-0
lines changed

4 files changed

+84
-0
lines changed

packages/toolkit/src/query/core/buildThunks.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import type {
2828
QueryDefinition,
2929
ResultDescription,
3030
ResultTypeFrom,
31+
SchemaFailureConverter,
3132
SchemaFailureHandler,
3233
SchemaFailureInfo,
3334
} from '../endpointDefinitions'
@@ -333,6 +334,7 @@ export function buildThunks<
333334
assertTagType,
334335
selectors,
335336
onSchemaFailure,
337+
catchSchemaFailure: globalCatchSchemaFailure,
336338
skipSchemaValidation: globalSkipSchemaValidation,
337339
}: {
338340
baseQuery: BaseQuery
@@ -343,6 +345,7 @@ export function buildThunks<
343345
assertTagType: AssertTagTypes
344346
selectors: AllSelectors
345347
onSchemaFailure: SchemaFailureHandler | undefined
348+
catchSchemaFailure: SchemaFailureConverter<BaseQuery> | undefined
346349
skipSchemaValidation: boolean | undefined
347350
}) {
348351
type State = RootState<any, string, ReducerPath>
@@ -802,6 +805,22 @@ export function buildThunks<
802805
caughtError = e
803806
}
804807
}
808+
if (caughtError instanceof NamedSchemaError) {
809+
const info: SchemaFailureInfo = {
810+
endpoint: arg.endpointName,
811+
arg: arg.originalArgs,
812+
type: arg.type,
813+
queryCacheKey: arg.type === 'query' ? arg.queryCacheKey : undefined,
814+
}
815+
const { catchSchemaFailure = globalCatchSchemaFailure } =
816+
endpointDefinition
817+
if (catchSchemaFailure) {
818+
return rejectWithValue(
819+
catchSchemaFailure(caughtError, info),
820+
addShouldAutoBatch({ baseQueryMeta: {} }), // TODO: how do we get meta here?
821+
)
822+
}
823+
}
805824
if (
806825
typeof process !== 'undefined' &&
807826
process.env.NODE_ENV !== 'production'

packages/toolkit/src/query/core/module.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -517,6 +517,7 @@ export const coreModule = ({
517517
refetchOnReconnect,
518518
invalidationBehavior,
519519
onSchemaFailure,
520+
catchSchemaFailure,
520521
skipSchemaValidation,
521522
},
522523
context,
@@ -585,6 +586,7 @@ export const coreModule = ({
585586
assertTagType,
586587
selectors,
587588
onSchemaFailure,
589+
catchSchemaFailure,
588590
skipSchemaValidation,
589591
})
590592

packages/toolkit/src/query/createApi.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { defaultSerializeQueryArgs } from './defaultSerializeQueryArgs'
66
import type {
77
EndpointBuilder,
88
EndpointDefinitions,
9+
SchemaFailureConverter,
910
SchemaFailureHandler,
1011
} from './endpointDefinitions'
1112
import {
@@ -244,6 +245,35 @@ export interface CreateApiOptions<
244245
* ```
245246
*/
246247
onSchemaFailure?: SchemaFailureHandler
248+
249+
/**
250+
* Convert a schema validation failure into an error shape matching base query errors.
251+
*
252+
* When not provided, schema failures are treated as fatal, and normal error handling such as tag invalidation will not be executed.
253+
*
254+
* @example
255+
* ```ts
256+
* // codeblock-meta no-transpile
257+
* import { createApi } from '@reduxjs/toolkit/query/react'
258+
* import * as v from "valibot"
259+
*
260+
* const api = createApi({
261+
* baseQuery: fetchBaseQuery({ baseUrl: '/' }),
262+
* endpoints: (build) => ({
263+
* getPost: build.query<Post, { id: number }>({
264+
* query: ({ id }) => `/post/${id}`,
265+
* responseSchema: v.object({ id: v.number(), name: v.string() }),
266+
* }),
267+
* }),
268+
* catchSchemaFailure: (error, info) => ({
269+
* status: "CUSTOM_ERROR",
270+
* error: error.schemaName + " failed validation",
271+
* data: error.issues,
272+
* }),
273+
* })
274+
*/
275+
catchSchemaFailure?: SchemaFailureConverter<BaseQuery>
276+
247277
/**
248278
* Defaults to `false`.
249279
*

packages/toolkit/src/query/endpointDefinitions.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,11 @@ export type SchemaFailureHandler = (
5656
info: SchemaFailureInfo,
5757
) => void
5858

59+
export type SchemaFailureConverter<BaseQuery extends BaseQueryFn> = (
60+
error: NamedSchemaError,
61+
info: SchemaFailureInfo,
62+
) => BaseQueryError<BaseQuery>
63+
5964
export type EndpointDefinitionWithQuery<
6065
QueryArg,
6166
BaseQuery extends BaseQueryFn,
@@ -380,6 +385,34 @@ interface CommonEndpointDefinition<
380385
* ```
381386
*/
382387
onSchemaFailure?: SchemaFailureHandler
388+
/**
389+
* Convert a schema validation failure into an error shape matching base query errors.
390+
*
391+
* When not provided, schema failures are treated as fatal, and normal error handling such as tag invalidation will not be executed.
392+
*
393+
* @example
394+
* ```ts
395+
* // codeblock-meta no-transpile
396+
* import { createApi } from '@reduxjs/toolkit/query/react'
397+
* import * as v from "valibot"
398+
*
399+
* const api = createApi({
400+
* baseQuery: fetchBaseQuery({ baseUrl: '/' }),
401+
* endpoints: (build) => ({
402+
* getPost: build.query<Post, { id: number }>({
403+
* query: ({ id }) => `/post/${id}`,
404+
* responseSchema: v.object({ id: v.number(), name: v.string() }),
405+
* catchSchemaFailure: (error, info) => ({
406+
* status: "CUSTOM_ERROR",
407+
* error: error.schemaName + " failed validation",
408+
* data: error.issues,
409+
* }),
410+
* }),
411+
* }),
412+
* })
413+
* ```
414+
*/
415+
catchSchemaFailure?: SchemaFailureConverter<BaseQuery>
383416

384417
/**
385418
* Defaults to `false`.

0 commit comments

Comments
 (0)