Skip to content

Commit 1946176

Browse files
committed
add api level and endpoint level handlers for schema failures
1 parent 30c44e5 commit 1946176

File tree

6 files changed

+195
-49
lines changed

6 files changed

+195
-49
lines changed

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

Lines changed: 79 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ import type {
2727
QueryArgFrom,
2828
QueryDefinition,
2929
ResultTypeFrom,
30+
SchemaFailureHandler,
31+
SchemaFailureInfo,
3032
} from '../endpointDefinitions'
3133
import {
3234
calculateProvidedBy,
@@ -64,7 +66,7 @@ import {
6466
isRejectedWithValue,
6567
SHOULD_AUTOBATCH,
6668
} from './rtkImports'
67-
import { parseWithSchema } from '../standardSchema'
69+
import { parseWithSchema, NamedSchemaError } from '../standardSchema'
6870

6971
export type BuildThunksApiEndpointQuery<
7072
Definition extends QueryDefinition<any, any, any, any, any>,
@@ -324,6 +326,7 @@ export function buildThunks<
324326
api,
325327
assertTagType,
326328
selectors,
329+
onSchemaFailure,
327330
}: {
328331
baseQuery: BaseQuery
329332
reducerPath: ReducerPath
@@ -332,6 +335,7 @@ export function buildThunks<
332335
api: Api<BaseQuery, Definitions, ReducerPath, any>
333336
assertTagType: AssertTagTypes
334337
selectors: AllSelectors
338+
onSchemaFailure: SchemaFailureHandler | undefined
335339
}) {
336340
type State = RootState<any, string, ReducerPath>
337341

@@ -555,7 +559,11 @@ export function buildThunks<
555559
endpointDefinition
556560

557561
if (argSchema) {
558-
finalQueryArg = await parseWithSchema(argSchema, finalQueryArg)
562+
finalQueryArg = await parseWithSchema(
563+
argSchema,
564+
finalQueryArg,
565+
'argSchema',
566+
)
559567
}
560568

561569
if (forceQueryFn) {
@@ -614,7 +622,11 @@ export function buildThunks<
614622
let { data } = result
615623

616624
if (rawResponseSchema) {
617-
data = await parseWithSchema(rawResponseSchema, result.data)
625+
data = await parseWithSchema(
626+
rawResponseSchema,
627+
result.data,
628+
'rawResponseSchema',
629+
)
618630
}
619631

620632
let transformedResponse = await transformResponse(
@@ -627,6 +639,7 @@ export function buildThunks<
627639
transformedResponse = await parseWithSchema(
628640
responseSchema,
629641
transformedResponse,
642+
'responseSchema',
630643
)
631644
}
632645

@@ -723,6 +736,7 @@ export function buildThunks<
723736
finalQueryReturnValue.meta = await parseWithSchema(
724737
metaSchema,
725738
finalQueryReturnValue.meta,
739+
'metaSchema',
726740
)
727741
}
728742

@@ -735,59 +749,78 @@ export function buildThunks<
735749
}),
736750
)
737751
} catch (error) {
738-
let caughtError = error
739-
if (caughtError instanceof HandledError) {
740-
let transformErrorResponse = getTransformCallbackForEndpoint(
741-
endpointDefinition,
742-
'transformErrorResponse',
743-
)
744-
const { rawErrorResponseSchema, errorResponseSchema } =
745-
endpointDefinition
752+
try {
753+
let caughtError = error
754+
if (caughtError instanceof HandledError) {
755+
let transformErrorResponse = getTransformCallbackForEndpoint(
756+
endpointDefinition,
757+
'transformErrorResponse',
758+
)
759+
const { rawErrorResponseSchema, errorResponseSchema } =
760+
endpointDefinition
746761

747-
let { value, meta } = caughtError
762+
let { value, meta } = caughtError
748763

749-
if (rawErrorResponseSchema) {
750-
value = await parseWithSchema(rawErrorResponseSchema, value)
751-
}
764+
if (rawErrorResponseSchema) {
765+
value = await parseWithSchema(
766+
rawErrorResponseSchema,
767+
value,
768+
'rawErrorResponseSchema',
769+
)
770+
}
752771

753-
if (metaSchema) {
754-
meta = await parseWithSchema(metaSchema, meta)
755-
}
772+
if (metaSchema) {
773+
meta = await parseWithSchema(metaSchema, meta, 'metaSchema')
774+
}
756775

757-
try {
758-
let transformedErrorResponse = await transformErrorResponse(
759-
value,
760-
meta,
761-
arg.originalArgs,
762-
)
763-
if (errorResponseSchema) {
764-
transformedErrorResponse = await parseWithSchema(
765-
errorResponseSchema,
776+
try {
777+
let transformedErrorResponse = await transformErrorResponse(
778+
value,
779+
meta,
780+
arg.originalArgs,
781+
)
782+
if (errorResponseSchema) {
783+
transformedErrorResponse = await parseWithSchema(
784+
errorResponseSchema,
785+
transformedErrorResponse,
786+
'errorResponseSchema',
787+
)
788+
}
789+
790+
return rejectWithValue(
766791
transformedErrorResponse,
792+
addShouldAutoBatch({ baseQueryMeta: meta }),
767793
)
794+
} catch (e) {
795+
caughtError = e
768796
}
769-
770-
return rejectWithValue(
771-
transformedErrorResponse,
772-
addShouldAutoBatch({ baseQueryMeta: meta }),
773-
)
774-
} catch (e) {
775-
caughtError = e
776797
}
777-
}
778-
if (
779-
typeof process !== 'undefined' &&
780-
process.env.NODE_ENV !== 'production'
781-
) {
782-
console.error(
783-
`An unhandled error occurred processing a request for the endpoint "${arg.endpointName}".
798+
if (
799+
typeof process !== 'undefined' &&
800+
process.env.NODE_ENV !== 'production'
801+
) {
802+
console.error(
803+
`An unhandled error occurred processing a request for the endpoint "${arg.endpointName}".
784804
In the case of an unhandled error, no tags will be "provided" or "invalidated".`,
785-
caughtError,
786-
)
787-
} else {
788-
console.error(caughtError)
805+
caughtError,
806+
)
807+
} else {
808+
console.error(caughtError)
809+
}
810+
throw caughtError
811+
} catch (error) {
812+
if (error instanceof NamedSchemaError) {
813+
const info: SchemaFailureInfo = {
814+
endpoint: arg.endpointName,
815+
arg: arg.originalArgs,
816+
type: arg.type,
817+
queryCacheKey: arg.type === 'query' ? arg.queryCacheKey : undefined,
818+
}
819+
endpointDefinition.onSchemaFailure?.(error, info)
820+
onSchemaFailure?.(error, info)
821+
}
822+
throw error
789823
}
790-
throw caughtError
791824
}
792825
}
793826

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -515,6 +515,7 @@ export const coreModule = ({
515515
refetchOnFocus,
516516
refetchOnReconnect,
517517
invalidationBehavior,
518+
onSchemaFailure,
518519
},
519520
context,
520521
) {
@@ -581,6 +582,7 @@ export const coreModule = ({
581582
serializeQueryArgs,
582583
assertTagType,
583584
selectors,
585+
onSchemaFailure,
584586
})
585587

586588
const { reducer, actions: sliceActions } = buildSlice({

packages/toolkit/src/query/createApi.ts

Lines changed: 3 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+
SchemaFailureHandler,
910
} from './endpointDefinitions'
1011
import {
1112
DefinitionType,
@@ -212,6 +213,8 @@ export interface CreateApiOptions<
212213
NoInfer<TagTypes>,
213214
NoInfer<ReducerPath>
214215
>
216+
217+
onSchemaFailure?: SchemaFailureHandler
215218
}
216219

217220
export type CreateApi<Modules extends ModuleName> = {

packages/toolkit/src/query/endpointDefinitions.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,23 @@ import type {
3838
UnwrapPromise,
3939
} from './tsHelpers'
4040
import { isNotNullish } from './utils'
41+
import type { NamedSchemaError } from './standardSchema'
4142

4243
const resultType = /* @__PURE__ */ Symbol()
4344
const baseQuery = /* @__PURE__ */ Symbol()
4445

46+
export interface SchemaFailureInfo {
47+
endpoint: string
48+
arg: any
49+
type: 'query' | 'mutation'
50+
queryCacheKey?: string
51+
}
52+
53+
export type SchemaFailureHandler = (
54+
error: NamedSchemaError,
55+
info: SchemaFailureInfo,
56+
) => void
57+
4558
type EndpointDefinitionWithQuery<
4659
QueryArg,
4760
BaseQuery extends BaseQueryFn,
@@ -222,6 +235,8 @@ export type BaseEndpointDefinition<
222235
*/
223236
structuralSharing?: boolean
224237

238+
onSchemaFailure?: SchemaFailureHandler
239+
225240
/* phantom type */
226241
[resultType]?: ResultType
227242
/* phantom type */
Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,24 @@
11
import type { StandardSchemaV1 } from '@standard-schema/spec'
22
import { SchemaError } from '@standard-schema/utils'
33

4+
export class NamedSchemaError extends SchemaError {
5+
constructor(
6+
issues: readonly StandardSchemaV1.Issue[],
7+
public readonly value: any,
8+
public readonly schemaName: string,
9+
) {
10+
super(issues)
11+
}
12+
}
13+
414
export async function parseWithSchema<Schema extends StandardSchemaV1>(
515
schema: Schema,
616
data: unknown,
17+
schemaName: string,
718
): Promise<StandardSchemaV1.InferOutput<Schema>> {
8-
let result = schema['~standard'].validate(data)
9-
if (result instanceof Promise) result = await result
10-
if (result.issues) throw new SchemaError(result.issues)
19+
const result = await schema['~standard'].validate(data)
20+
if (result.issues) {
21+
throw new NamedSchemaError(result.issues, data, schemaName)
22+
}
1123
return result.value
1224
}

0 commit comments

Comments
 (0)