Skip to content

Commit a789d0a

Browse files
authoredSep 8, 2024··
refactor: pass utils to validation errors shape customizer functions (#263)
Code in this PR adds a second argument to `handleValidationErrorsShape` and `handleBindArgsValidationErrorsShape` functions, called `utils`, which is an object that contains `clientInput`, `bindArgsClientInputs`, `metadata` and `ctx` properties. This addition allows you to set dynamic validation errors based on current action execution data. re #256
1 parent 3d32f9d commit a789d0a

File tree

4 files changed

+82
-23
lines changed

4 files changed

+82
-23
lines changed
 

‎packages/next-safe-action/src/action-builder.ts

+31-5
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,8 @@ export function actionBuilder<
4646
bindArgsSchemas?: BAS;
4747
outputSchema?: OS;
4848
validationAdapter: ValidationAdapter;
49-
handleValidationErrorsShape: HandleValidationErrorsShapeFn<IS, CVE>;
50-
handleBindArgsValidationErrorsShape: HandleBindArgsValidationErrorsShapeFn<BAS, CBAVE>;
49+
handleValidationErrorsShape: HandleValidationErrorsShapeFn<IS, BAS, MD, Ctx, CVE>;
50+
handleBindArgsValidationErrorsShape: HandleBindArgsValidationErrorsShapeFn<IS, BAS, MD, Ctx, CBAVE>;
5151
metadataSchema: MetadataSchema;
5252
metadata: MD;
5353
handleServerError: NonNullable<SafeActionClientOpts<ServerError, MetadataSchema, any>["handleServerError"]>;
@@ -179,7 +179,14 @@ export function actionBuilder<
179179
const validationErrors = buildValidationErrors<IS>(parsedInput.issues);
180180

181181
middlewareResult.validationErrors = await Promise.resolve(
182-
args.handleValidationErrorsShape(validationErrors)
182+
args.handleValidationErrorsShape(validationErrors, {
183+
clientInput: clientInputs.at(-1) as IS extends Schema ? InferIn<IS> : undefined,
184+
bindArgsClientInputs: (bindArgsSchemas.length
185+
? clientInputs.slice(0, -1)
186+
: []) as InferInArray<BAS>,
187+
ctx: currentCtx as Ctx,
188+
metadata: args.metadata as MetadataSchema extends Schema ? Infer<MetadataSchema> : undefined,
189+
})
183190
);
184191
}
185192
}
@@ -188,7 +195,17 @@ export function actionBuilder<
188195
// If there are bind args validation errors, format them and store them in the middleware result.
189196
if (hasBindValidationErrors) {
190197
middlewareResult.bindArgsValidationErrors = await Promise.resolve(
191-
args.handleBindArgsValidationErrorsShape(bindArgsValidationErrors as BindArgsValidationErrors<BAS>)
198+
args.handleBindArgsValidationErrorsShape(
199+
bindArgsValidationErrors as BindArgsValidationErrors<BAS>,
200+
{
201+
clientInput: clientInputs.at(-1) as IS extends Schema ? InferIn<IS> : undefined,
202+
bindArgsClientInputs: (bindArgsSchemas.length
203+
? clientInputs.slice(0, -1)
204+
: []) as InferInArray<BAS>,
205+
ctx: currentCtx as Ctx,
206+
metadata: args.metadata as MetadataSchema extends Schema ? Infer<MetadataSchema> : undefined,
207+
}
208+
)
192209
);
193210
}
194211

@@ -241,7 +258,16 @@ export function actionBuilder<
241258
// If error is `ActionServerValidationError`, return `validationErrors` as if schema validation would fail.
242259
if (e instanceof ActionServerValidationError) {
243260
const ve = e.validationErrors as ValidationErrors<IS>;
244-
middlewareResult.validationErrors = await Promise.resolve(args.handleValidationErrorsShape(ve));
261+
middlewareResult.validationErrors = await Promise.resolve(
262+
args.handleValidationErrorsShape(ve, {
263+
clientInput: clientInputs.at(-1) as IS extends Schema ? InferIn<IS> : undefined,
264+
bindArgsClientInputs: (bindArgsSchemas.length
265+
? clientInputs.slice(0, -1)
266+
: []) as InferInArray<BAS>,
267+
ctx: currentCtx as Ctx,
268+
metadata: args.metadata as MetadataSchema extends Schema ? Infer<MetadataSchema> : undefined,
269+
})
270+
);
245271
} else {
246272
// If error is not an instance of Error, wrap it in an Error object with
247273
// the default message.

‎packages/next-safe-action/src/safe-action-client.ts

+17-10
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,8 @@ export class SafeActionClient<
4242
readonly #ctxType: Ctx;
4343
readonly #bindArgsSchemas: BAS;
4444
readonly #validationAdapter: ValidationAdapter;
45-
readonly #handleValidationErrorsShape: HandleValidationErrorsShapeFn<IS, CVE>;
46-
readonly #handleBindArgsValidationErrorsShape: HandleBindArgsValidationErrorsShapeFn<BAS, CBAVE>;
45+
readonly #handleValidationErrorsShape: HandleValidationErrorsShapeFn<IS, BAS, MD, Ctx, CVE>;
46+
readonly #handleBindArgsValidationErrorsShape: HandleBindArgsValidationErrorsShapeFn<IS, BAS, MD, Ctx, CBAVE>;
4747
readonly #defaultValidationErrorsShape: ODVES;
4848
readonly #throwValidationErrors: boolean;
4949

@@ -56,8 +56,8 @@ export class SafeActionClient<
5656
outputSchema: OS;
5757
bindArgsSchemas: BAS;
5858
validationAdapter: ValidationAdapter;
59-
handleValidationErrorsShape: HandleValidationErrorsShapeFn<IS, CVE>;
60-
handleBindArgsValidationErrorsShape: HandleBindArgsValidationErrorsShapeFn<BAS, CBAVE>;
59+
handleValidationErrorsShape: HandleValidationErrorsShapeFn<IS, BAS, MD, Ctx, CVE>;
60+
handleBindArgsValidationErrorsShape: HandleBindArgsValidationErrorsShapeFn<IS, BAS, MD, Ctx, CBAVE>;
6161
ctxType: Ctx;
6262
} & Required<
6363
Pick<
@@ -143,7 +143,7 @@ export class SafeActionClient<
143143
>(
144144
inputSchema: OIS,
145145
utils?: {
146-
handleValidationErrorsShape?: HandleValidationErrorsShapeFn<AIS, OCVE>;
146+
handleValidationErrorsShape?: HandleValidationErrorsShapeFn<AIS, BAS, MD, Ctx, OCVE>;
147147
}
148148
) {
149149
return new SafeActionClient({
@@ -163,8 +163,9 @@ export class SafeActionClient<
163163
outputSchema: this.#outputSchema,
164164
validationAdapter: this.#validationAdapter,
165165
handleValidationErrorsShape: (utils?.handleValidationErrorsShape ??
166-
this.#handleValidationErrorsShape) as HandleValidationErrorsShapeFn<AIS, OCVE>,
167-
handleBindArgsValidationErrorsShape: this.#handleBindArgsValidationErrorsShape,
166+
this.#handleValidationErrorsShape) as HandleValidationErrorsShapeFn<AIS, BAS, MD, Ctx, OCVE>,
167+
handleBindArgsValidationErrorsShape: this
168+
.#handleBindArgsValidationErrorsShape as HandleBindArgsValidationErrorsShapeFn<AIS, BAS, MD, Ctx, CBAVE>,
168169
ctxType: {} as Ctx,
169170
defaultValidationErrorsShape: this.#defaultValidationErrorsShape,
170171
throwValidationErrors: this.#throwValidationErrors,
@@ -185,7 +186,7 @@ export class SafeActionClient<
185186
: BindArgsValidationErrors<OBAS>,
186187
>(
187188
bindArgsSchemas: OBAS,
188-
utils?: { handleBindArgsValidationErrorsShape?: HandleBindArgsValidationErrorsShapeFn<OBAS, OCBAVE> }
189+
utils?: { handleBindArgsValidationErrorsShape?: HandleBindArgsValidationErrorsShapeFn<IS, OBAS, MD, Ctx, OCBAVE> }
189190
) {
190191
return new SafeActionClient({
191192
middlewareFns: this.#middlewareFns,
@@ -196,9 +197,15 @@ export class SafeActionClient<
196197
bindArgsSchemas,
197198
outputSchema: this.#outputSchema,
198199
validationAdapter: this.#validationAdapter,
199-
handleValidationErrorsShape: this.#handleValidationErrorsShape,
200+
handleValidationErrorsShape: this.#handleValidationErrorsShape as unknown as HandleValidationErrorsShapeFn<
201+
IS,
202+
OBAS,
203+
MD,
204+
Ctx,
205+
CVE
206+
>,
200207
handleBindArgsValidationErrorsShape: (utils?.handleBindArgsValidationErrorsShape ??
201-
this.#handleBindArgsValidationErrorsShape) as HandleBindArgsValidationErrorsShapeFn<OBAS, OCBAVE>,
208+
this.#handleBindArgsValidationErrorsShape) as HandleBindArgsValidationErrorsShapeFn<IS, OBAS, MD, Ctx, OCBAVE>,
202209
ctxType: {} as Ctx,
203210
defaultValidationErrorsShape: this.#defaultValidationErrorsShape,
204211
throwValidationErrors: this.#throwValidationErrors,

‎packages/next-safe-action/src/validation-errors.types.ts

+29-5
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { Infer, Schema } from "./adapters/types";
1+
import type { Infer, InferIn, Schema } from "./adapters/types";
22
import type { Prettify } from "./utils.types";
33

44
// Object with an optional list of validation errors.
@@ -46,13 +46,37 @@ export type FlattenedBindArgsValidationErrors<BAVE extends readonly ValidationEr
4646
/**
4747
* Type of the function used to format validation errors.
4848
*/
49-
export type HandleValidationErrorsShapeFn<S extends Schema | undefined, CVE> = (
50-
validationErrors: ValidationErrors<S>
49+
export type HandleValidationErrorsShapeFn<
50+
S extends Schema | undefined,
51+
BAS extends readonly Schema[],
52+
MD,
53+
Ctx extends object,
54+
CVE,
55+
> = (
56+
validationErrors: ValidationErrors<S>,
57+
utils: {
58+
clientInput: S extends Schema ? InferIn<S> : undefined;
59+
bindArgsClientInputs: BAS;
60+
metadata: MD;
61+
ctx: Prettify<Ctx>;
62+
}
5163
) => CVE;
5264

5365
/**
5466
* Type of the function used to format bind arguments validation errors.
5567
*/
56-
export type HandleBindArgsValidationErrorsShapeFn<BAS extends readonly Schema[], CBAVE> = (
57-
bindArgsValidationErrors: BindArgsValidationErrors<BAS>
68+
export type HandleBindArgsValidationErrorsShapeFn<
69+
S extends Schema | undefined,
70+
BAS extends readonly Schema[],
71+
MD,
72+
Ctx extends object,
73+
CBAVE,
74+
> = (
75+
bindArgsValidationErrors: BindArgsValidationErrors<BAS>,
76+
utils: {
77+
clientInput: S extends Schema ? InferIn<S> : undefined;
78+
bindArgsClientInputs: BAS;
79+
metadata: MD;
80+
ctx: Prettify<Ctx>;
81+
}
5882
) => CBAVE;

‎website/docs/define-actions/validation-errors.md

+5-3
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ This can be customized both at the safe action client level and at the action le
1313
- using [`defaultValidationErrorsShape`](/docs/define-actions/create-the-client#defaultvalidationerrorsshape) optional property in `createSafeActionClient`;
1414
- using `handleValidationErrorsShape` and `handleBindArgsValidationErrorsShape` optional functions in [`schema`](/docs/define-actions/instance-methods#schema) and [`bindArgsSchemas`](/docs/define-actions/instance-methods#bindargsschemas) methods.
1515

16-
The second way overrides the shape set at the instance level, per action. More information below.
16+
The second way overrides the shape set at the instance level, per action.
1717

1818
For example, if you want to flatten the validation errors (emulation of Zod's [`flatten`](https://zod.dev/ERROR_HANDLING?id=flattening-errors) method), you can (but not required to) use the `flattenValidationErrors` utility function exported from the library, combining it with `handleValidationErrorsShape` inside `schema` method:
1919

@@ -39,19 +39,21 @@ export const loginUser = actionClient
3939
// Here we use the `flattenValidationErrors` function to customize the returned validation errors
4040
// object to the client.
4141
// highlight-next-line
42-
handleValidationErrorsShape: (ve) => flattenValidationErrors(ve).fieldErrors,
42+
handleValidationErrorsShape: (ve, utils) => flattenValidationErrors(ve).fieldErrors,
4343
})
4444
.bindArgsSchemas(bindArgsSchemas, {
4545
// Here we use the `flattenBindArgsValidatonErrors` function to customize the returned bind args
4646
// validation errors object array to the client.
4747
// highlight-next-line
48-
handleBindArgsValidationErrors: (ve) => flattenBindArgsValidationErrors(ve),
48+
handleBindArgsValidationErrors: (ve, utils) => flattenBindArgsValidationErrors(ve),
4949
})
5050
.action(async ({ parsedInput: { username, password } }) => {
5151
// Your code here...
5252
});
5353
```
5454

55+
The second argument of both `handleValidationErrorsShape` and `handleBindArgsValidationErrors` functions is an `utils` object that contains info about the current action execution (`clientInput`, `bindArgsClientInputs`, `metadata` and `ctx` properties). It's passed to the functions to allow granular and dynamic customization of the validation errors shape.
56+
5557
:::note
5658
If you chain multiple `schema` methods, as explained in the [Extend previous schema](/docs/define-actions/extend-previous-schemas) page, and want to override the default validation errors shape, you **must** use `handleValidationErrorsShape` inside the last `schema` method, otherwise there would be a type mismatch in the returned action result.
5759
:::

0 commit comments

Comments
 (0)
Please sign in to comment.