Skip to content

Commit 64fb643

Browse files
authoredFeb 2, 2024
feat(validation-errors): support setting validation errors in action's server code function (#52)
In some cases it's very useful, if not essential, to set custom validation errors during action's server code execution. This commit adds a function called `returnValidationErrors` that allows you to set custom validation errors when defining actions. Big thanks to @theboxer for the implementation.
1 parent 6ab9b5c commit 64fb643

File tree

5 files changed

+44
-12
lines changed

5 files changed

+44
-12
lines changed
 
+10-9
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,21 @@
11
"use server";
22

33
import { action } from "@/lib/safe-action";
4+
import { returnValidationErrors } from "next-safe-action";
45
import { z } from "zod";
56

67
const input = z.object({
78
username: z.string().min(3).max(10),
89
password: z.string().min(8).max(100),
910
});
1011

11-
export const loginUser = action(input, async ({ username, password }) => {
12+
export const loginUser = action(input, async ({ username, password }, ctx) => {
1213
if (username === "johndoe") {
13-
return {
14-
error: {
15-
reason: "user_suspended",
14+
returnValidationErrors(input, {
15+
username: {
16+
_errors: ["user_suspended"],
1617
},
17-
};
18+
});
1819
}
1920

2021
if (username === "user" && password === "password") {
@@ -23,9 +24,9 @@ export const loginUser = action(input, async ({ username, password }) => {
2324
};
2425
}
2526

26-
return {
27-
error: {
28-
reason: "incorrect_credentials",
27+
returnValidationErrors(input, {
28+
username: {
29+
_errors: ["incorrect_credentials"],
2930
},
30-
};
31+
});
3132
});

‎packages/example-app/src/app/with-context/edituser-action.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ export const editUser = authAction(
1313
// Here you have access to `userId`, which comes from `buildContext`
1414
// return object in src/lib/safe-action.ts.
1515
// \\\\\
16-
async ({ fullName, age }, userId) => {
16+
async ({ fullName, age }, { userId }) => {
1717
if (fullName.toLowerCase() === "john doe") {
1818
return {
1919
error: {

‎packages/example-app/src/lib/safe-action.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ export const authAction = createSafeActionClient({
3939
parsedInput
4040
);
4141

42-
return userId;
42+
return { userId };
4343
},
4444
handleReturnedServerError,
4545
});

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

+31
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,11 @@ export const createSafeActionClient = <Context>(createOpts?: SafeClientOpts<Cont
9898
throw e;
9999
}
100100

101+
// If error is ServerValidationError, return validationErrors as if schema validation would fail.
102+
if (e instanceof ServerValidationError) {
103+
return { validationErrors: e.validationErrors as ValidationErrors<S> };
104+
}
105+
101106
// If error cannot be handled, warn the user and return a generic message.
102107
if (!isError(e)) {
103108
console.warn("Could not handle server error. Not an instance of Error: ", e);
@@ -115,3 +120,29 @@ export const createSafeActionClient = <Context>(createOpts?: SafeClientOpts<Cont
115120

116121
return actionBuilder;
117122
};
123+
124+
// VALIDATION ERRORS
125+
126+
// This class is internally used to throw validation errors in action's server code function, using
127+
// `returnValidationErrors`.
128+
class ServerValidationError<S extends Schema> extends Error {
129+
public validationErrors: ValidationErrors<S>;
130+
constructor(validationErrors: ValidationErrors<S>) {
131+
super("Server Validation Error");
132+
this.validationErrors = validationErrors;
133+
}
134+
}
135+
136+
/**
137+
* Return custom validation errors to the client from the action's server code function.
138+
* Code declared after this function invocation will not be executed.
139+
* @param schema Input schema
140+
* @param validationErrors Validation errors object
141+
* @throws {ServerValidationError}
142+
*/
143+
export function returnValidationErrors<S extends Schema>(
144+
schema: S,
145+
validationErrors: ValidationErrors<S>
146+
): never {
147+
throw new ServerValidationError<S>(validationErrors);
148+
}

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type { Infer, Schema, ValidationIssue } from "@decs/typeschema";
22

3-
export const isError = (error: any): error is Error => error instanceof Error;
3+
export const isError = (error: unknown): error is Error => error instanceof Error;
44

55
// UTIL TYPES
66

0 commit comments

Comments
 (0)
Please sign in to comment.