Skip to content

Commit d92f44d

Browse files
EskiMojo14markerikson
authored andcommitted
add api level and endpoint level handlers for schema failures
1 parent 51c0104 commit d92f44d

File tree

6 files changed

+195
-49
lines changed

6 files changed

+195
-49
lines changed

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

+79-46
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ import type {
2828
QueryDefinition,
2929
ResultDescription,
3030
ResultTypeFrom,
31+
SchemaFailureHandler,
32+
SchemaFailureInfo,
3133
} from '../endpointDefinitions'
3234
import {
3335
calculateProvidedBy,
@@ -65,7 +67,7 @@ import {
6567
isRejectedWithValue,
6668
SHOULD_AUTOBATCH,
6769
} from './rtkImports'
68-
import { parseWithSchema } from '../standardSchema'
70+
import { parseWithSchema, NamedSchemaError } from '../standardSchema'
6971

7072
export type BuildThunksApiEndpointQuery<
7173
Definition extends QueryDefinition<any, any, any, any, any>,
@@ -330,6 +332,7 @@ export function buildThunks<
330332
api,
331333
assertTagType,
332334
selectors,
335+
onSchemaFailure,
333336
}: {
334337
baseQuery: BaseQuery
335338
reducerPath: ReducerPath
@@ -338,6 +341,7 @@ export function buildThunks<
338341
api: Api<BaseQuery, Definitions, ReducerPath, any>
339342
assertTagType: AssertTagTypes
340343
selectors: AllSelectors
344+
onSchemaFailure: SchemaFailureHandler | undefined
341345
}) {
342346
type State = RootState<any, string, ReducerPath>
343347

@@ -559,7 +563,11 @@ export function buildThunks<
559563
endpointDefinition
560564

561565
if (argSchema) {
562-
finalQueryArg = await parseWithSchema(argSchema, finalQueryArg)
566+
finalQueryArg = await parseWithSchema(
567+
argSchema,
568+
finalQueryArg,
569+
'argSchema',
570+
)
563571
}
564572

565573
if (forceQueryFn) {
@@ -618,7 +626,11 @@ export function buildThunks<
618626
let { data } = result
619627

620628
if (rawResponseSchema) {
621-
data = await parseWithSchema(rawResponseSchema, result.data)
629+
data = await parseWithSchema(
630+
rawResponseSchema,
631+
result.data,
632+
'rawResponseSchema',
633+
)
622634
}
623635

624636
let transformedResponse = await transformResponse(
@@ -631,6 +643,7 @@ export function buildThunks<
631643
transformedResponse = await parseWithSchema(
632644
responseSchema,
633645
transformedResponse,
646+
'responseSchema',
634647
)
635648
}
636649

@@ -727,6 +740,7 @@ export function buildThunks<
727740
finalQueryReturnValue.meta = await parseWithSchema(
728741
metaSchema,
729742
finalQueryReturnValue.meta,
743+
'metaSchema',
730744
)
731745
}
732746

@@ -739,59 +753,78 @@ export function buildThunks<
739753
}),
740754
)
741755
} catch (error) {
742-
let caughtError = error
743-
if (caughtError instanceof HandledError) {
744-
let transformErrorResponse = getTransformCallbackForEndpoint(
745-
endpointDefinition,
746-
'transformErrorResponse',
747-
)
748-
const { rawErrorResponseSchema, errorResponseSchema } =
749-
endpointDefinition
756+
try {
757+
let caughtError = error
758+
if (caughtError instanceof HandledError) {
759+
let transformErrorResponse = getTransformCallbackForEndpoint(
760+
endpointDefinition,
761+
'transformErrorResponse',
762+
)
763+
const { rawErrorResponseSchema, errorResponseSchema } =
764+
endpointDefinition
750765

751-
let { value, meta } = caughtError
766+
let { value, meta } = caughtError
752767

753-
if (rawErrorResponseSchema) {
754-
value = await parseWithSchema(rawErrorResponseSchema, value)
755-
}
768+
if (rawErrorResponseSchema) {
769+
value = await parseWithSchema(
770+
rawErrorResponseSchema,
771+
value,
772+
'rawErrorResponseSchema',
773+
)
774+
}
756775

757-
if (metaSchema) {
758-
meta = await parseWithSchema(metaSchema, meta)
759-
}
776+
if (metaSchema) {
777+
meta = await parseWithSchema(metaSchema, meta, 'metaSchema')
778+
}
760779

761-
try {
762-
let transformedErrorResponse = await transformErrorResponse(
763-
value,
764-
meta,
765-
arg.originalArgs,
766-
)
767-
if (errorResponseSchema) {
768-
transformedErrorResponse = await parseWithSchema(
769-
errorResponseSchema,
780+
try {
781+
let transformedErrorResponse = await transformErrorResponse(
782+
value,
783+
meta,
784+
arg.originalArgs,
785+
)
786+
if (errorResponseSchema) {
787+
transformedErrorResponse = await parseWithSchema(
788+
errorResponseSchema,
789+
transformedErrorResponse,
790+
'errorResponseSchema',
791+
)
792+
}
793+
794+
return rejectWithValue(
770795
transformedErrorResponse,
796+
addShouldAutoBatch({ baseQueryMeta: meta }),
771797
)
798+
} catch (e) {
799+
caughtError = e
772800
}
773-
774-
return rejectWithValue(
775-
transformedErrorResponse,
776-
addShouldAutoBatch({ baseQueryMeta: meta }),
777-
)
778-
} catch (e) {
779-
caughtError = e
780801
}
781-
}
782-
if (
783-
typeof process !== 'undefined' &&
784-
process.env.NODE_ENV !== 'production'
785-
) {
786-
console.error(
787-
`An unhandled error occurred processing a request for the endpoint "${arg.endpointName}".
802+
if (
803+
typeof process !== 'undefined' &&
804+
process.env.NODE_ENV !== 'production'
805+
) {
806+
console.error(
807+
`An unhandled error occurred processing a request for the endpoint "${arg.endpointName}".
788808
In the case of an unhandled error, no tags will be "provided" or "invalidated".`,
789-
caughtError,
790-
)
791-
} else {
792-
console.error(caughtError)
809+
caughtError,
810+
)
811+
} else {
812+
console.error(caughtError)
813+
}
814+
throw caughtError
815+
} catch (error) {
816+
if (error instanceof NamedSchemaError) {
817+
const info: SchemaFailureInfo = {
818+
endpoint: arg.endpointName,
819+
arg: arg.originalArgs,
820+
type: arg.type,
821+
queryCacheKey: arg.type === 'query' ? arg.queryCacheKey : undefined,
822+
}
823+
endpointDefinition.onSchemaFailure?.(error, info)
824+
onSchemaFailure?.(error, info)
825+
}
826+
throw error
793827
}
794-
throw caughtError
795828
}
796829
}
797830

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

+2
Original file line numberDiff line numberDiff line change
@@ -516,6 +516,7 @@ export const coreModule = ({
516516
refetchOnFocus,
517517
refetchOnReconnect,
518518
invalidationBehavior,
519+
onSchemaFailure,
519520
},
520521
context,
521522
) {
@@ -582,6 +583,7 @@ export const coreModule = ({
582583
serializeQueryArgs,
583584
assertTagType,
584585
selectors,
586+
onSchemaFailure,
585587
})
586588

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

packages/toolkit/src/query/createApi.ts

+3
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

+15
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 */
+15-3
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)