Skip to content

Commit 9ae6764

Browse files
authoredApr 9, 2024··
feat(validation-errors): support flattening via flattenValidationErrors function (#100)
Sometimes it's better to deal with a flattened error object instead of a formatted one, for instance when you don't need to use nested objects in validation schemas. This PR exports a function called `flattenValidationErrors` that does what it says. Be aware that it works just one level deep, as it discards nested schema errors. This is a known limitation of this approach, since it can't prevent key conflicts. Suppose this is a returned formatted `validationErrors` object: ```typescript validationErrors = { _errors: ["Global error"], username: { _errors: ["Too short", "Username is invalid"], }, email: { _errors: ["Email is invalid"], } } ``` After passing it to `flattenValidationErrors`: ```typescript import { flattenValidationErrors } from "next-safe-action"; const flattenedErrors = flattenValidationErrors(validationErrors); ``` It becomes this: ```typescript flattenedErrors = { rootErrors: ["Global error"], fieldErrors: { username: ["Too short", "Username is invalid"], email: ["Email is invalid"], } } ```
1 parent 3eddac8 commit 9ae6764

File tree

3 files changed

+53
-7
lines changed

3 files changed

+53
-7
lines changed
 

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

+4-6
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { DEFAULT_SERVER_ERROR_MESSAGE, isError } from "./utils";
1616
import {
1717
ServerValidationError,
1818
buildValidationErrors,
19+
flattenValidationErrors,
1920
returnValidationErrors,
2021
} from "./validation-errors";
2122
import type { BindArgsValidationErrors, ValidationErrors } from "./validation-errors.types";
@@ -291,19 +292,16 @@ export const createSafeActionClient = <const ServerError = string>(
291292
});
292293
};
293294

294-
export {
295-
DEFAULT_SERVER_ERROR_MESSAGE,
296-
returnValidationErrors,
297-
type BindArgsValidationErrors,
298-
type ValidationErrors,
299-
};
295+
export { DEFAULT_SERVER_ERROR_MESSAGE, flattenValidationErrors, returnValidationErrors };
300296

301297
export type {
302298
ActionMetadata,
299+
BindArgsValidationErrors,
303300
MiddlewareFn,
304301
MiddlewareResult,
305302
SafeActionClientOpts,
306303
SafeActionFn,
307304
SafeActionResult,
308305
ServerCodeFn,
306+
ValidationErrors,
309307
};

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

+38-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
import type { ValidationIssue } from "@typeschema/core";
22
import type { Schema } from "@typeschema/main";
3-
import type { ErrorList, ValidationErrors } from "./validation-errors.types";
3+
import type {
4+
ErrorList,
5+
FlattenedValidationErrors,
6+
ValidationErrors,
7+
} from "./validation-errors.types";
48

59
// This function is used internally to build the validation errors object from a list of validation issues.
610
export const buildValidationErrors = <const S extends Schema>(issues: ValidationIssue[]) => {
@@ -69,3 +73,36 @@ export function returnValidationErrors<S extends Schema>(
6973
): never {
7074
throw new ServerValidationError<S>(validationErrors);
7175
}
76+
77+
/**
78+
* Transform default formatted validation errors into flattened structure.
79+
* `rootErrors` contains global errors, and `fieldErrors` contains errors for each field,
80+
* one level deep. It skips errors for nested fields.
81+
* @param {ValidationErrors} [validationErrors] Validation errors object
82+
* @returns {FlattenedValidationErrors} Flattened validation errors
83+
*/
84+
export function flattenValidationErrors<
85+
const S extends Schema,
86+
const VE extends ValidationErrors<S>,
87+
>(validationErrors?: VE) {
88+
const flattened: FlattenedValidationErrors<S, VE> = {
89+
rootErrors: [],
90+
fieldErrors: {},
91+
};
92+
93+
if (!validationErrors) {
94+
return flattened;
95+
}
96+
97+
for (const [key, value] of Object.entries<string[] | { _errors: string[] }>(validationErrors)) {
98+
if (key === "_errors" && Array.isArray(value)) {
99+
flattened.rootErrors = [...value];
100+
} else {
101+
if ("_errors" in value) {
102+
flattened.fieldErrors[key as keyof Omit<VE, "_errors">] = [...value._errors];
103+
}
104+
}
105+
}
106+
107+
return flattened;
108+
}

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

+11
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,14 @@ export type ValidationErrors<S extends Schema> = Extend<ErrorList & SchemaErrors
2424
export type BindArgsValidationErrors<BAS extends Schema[]> = (ValidationErrors<
2525
BAS[number]
2626
> | null)[];
27+
28+
/**
29+
* Type of flattened validation errors. `rootErrors` contains global errors, and `fieldErrors`
30+
* contains errors for each field, one level deep.
31+
*/
32+
export type FlattenedValidationErrors<S extends Schema, VE extends ValidationErrors<S>> = {
33+
rootErrors: string[];
34+
fieldErrors: {
35+
[K in keyof Omit<VE, "_errors">]?: string[];
36+
};
37+
};

0 commit comments

Comments
 (0)
Please sign in to comment.