Skip to content

Commit

Permalink
feat: merge server error handling functions into handleServerError (#…
Browse files Browse the repository at this point in the history
…257)

Code in this PR merges the functionality of `handleServerErrorLog` and
`handleReturnedServerError` functions into a single optional
initialization function called `handleServerError`. This change has been
made because having two functions for server error handling is
unnecessary, you can easily manage both logging and returned error
within a single function.
  • Loading branch information
TheEdoRan authored Sep 4, 2024
1 parent 4a74df9 commit c900720
Show file tree
Hide file tree
Showing 15 changed files with 79 additions and 132 deletions.
15 changes: 5 additions & 10 deletions apps/playground/src/lib/safe-action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,11 @@ export class ActionError extends Error {}

export const action = createSafeActionClient({
validationAdapter: zodAdapter(),
// You can provide a custom logging function, otherwise the lib will use `console.error`
// as the default logging system. If you want to disable server errors logging,
// just pass an empty Promise.
handleServerErrorLog: (e) => {
console.error(
"CUSTOM ERROR LOG FUNCTION, server error message:",
e.message
);
},
handleReturnedServerError: (e) => {
// You can provide a custom handler for server errors, otherwise the lib will use `console.error`
// as the default logging mechanism and will return the DEFAULT_SERVER_ERROR_MESSAGE for all server errors.
handleServerError: (e) => {
console.error("Action server error occurred:", e.message);

// If the error is an instance of `ActionError`, unmask the message.
if (e instanceof ActionError) {
return e.message;
Expand Down
2 changes: 1 addition & 1 deletion packages/next-safe-action/src/__tests__/metadata.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { zodAdapter } from "../adapters/zod";

const ac = createSafeActionClient({
validationAdapter: zodAdapter(),
handleServerErrorLog() {}, // disable server errors logging for these tests
handleServerError: () => DEFAULT_SERVER_ERROR_MESSAGE, // disable server errors logging for these tests
defineMetadataSchema() {
return z.object({
actionName: z.string(),
Expand Down
7 changes: 4 additions & 3 deletions packages/next-safe-action/src/__tests__/middleware.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { z } from "zod";
import {
createMiddleware,
createSafeActionClient,
DEFAULT_SERVER_ERROR_MESSAGE,
formatBindArgsValidationErrors,
formatValidationErrors,
returnValidationErrors,
Expand All @@ -14,8 +15,8 @@ import { zodAdapter } from "../adapters/zod";

const ac = createSafeActionClient({
validationAdapter: zodAdapter(),
handleServerErrorLog() {}, // disable server errors logging for these tests
handleReturnedServerError(e) {
handleServerError(e) {
// disable server error logging for these tests
return {
message: e.message,
};
Expand Down Expand Up @@ -296,7 +297,7 @@ test("server validation errors in execution result from middleware are correct",

const flac = createSafeActionClient({
validationAdapter: zodAdapter(),
handleServerErrorLog() {}, // disable server errors logging for these tests
handleServerError: () => DEFAULT_SERVER_ERROR_MESSAGE, // disable server errors logging for these tests
defaultValidationErrorsShape: "flattened",
});

Expand Down
20 changes: 10 additions & 10 deletions packages/next-safe-action/src/__tests__/server-error.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ class ActionError extends Error {

const ac1 = createSafeActionClient({
validationAdapter: zodAdapter(),
handleServerErrorLog: () => {}, // disable server errors logging for these tests
handleReturnedServerError(e) {
handleServerError(e) {
// disable server error logging for these tests
if (e instanceof ActionError) {
return e.message;
}
Expand Down Expand Up @@ -107,15 +107,15 @@ test("error occurred with `throwServerError` set to true at the action level thr
// Server error is an object with a 'message' property.
const ac2 = createSafeActionClient({
validationAdapter: zodAdapter(),
handleServerErrorLog: () => {}, // disable server errors logging for these tests
handleReturnedServerError(e) {
handleServerError(e) {
// disable server errors logging for these tests
return {
message: e.message,
};
},
});

test("error occurred in server code function has the correct shape defined by `handleReturnedServerError`", async () => {
test("error occurred in server code function has the correct shape defined by `handleServerError`", async () => {
const action = ac2.action(async () => {
throw new Error("Something bad happened");
});
Expand All @@ -129,7 +129,7 @@ test("error occurred in server code function has the correct shape defined by `h
assert.deepStrictEqual(actualResult, expectedResult);
});

test("error occurred in middleware function has the correct shape defined by `handleReturnedServerError`", async () => {
test("error occurred in middleware function has the correct shape defined by `handleServerError`", async () => {
const action = ac2
.use(async ({ next }) => next())
.use(async () => {
Expand All @@ -153,21 +153,21 @@ test("error occurred in middleware function has the correct shape defined by `ha
// Rethrow all server errors.
const ac3 = createSafeActionClient({
validationAdapter: zodAdapter(),
handleServerErrorLog: () => {}, // disable server errors logging for these tests
handleReturnedServerError(e) {
handleServerError(e) {
// disable server error logging for these tests
throw e;
},
});

test("action throws if an error occurred in server code function and `handleReturnedServerError` rethrows it", async () => {
test("action throws if an error occurred in server code function and `handleServerError` rethrows it", async () => {
const action = ac3.action(async () => {
throw new Error("Something bad happened");
});

assert.rejects(() => action());
});

test("action throws if an error occurred in middleware function and `handleReturnedServerError` rethrows it", async () => {
test("action throws if an error occurred in middleware function and `handleServerError` rethrows it", async () => {
const action = ac3
.use(async ({ next }) => next())
.use(async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,8 @@ test("action with invalid output data returns the default `serverError`", async
test("action with invalid output data throws an error of the correct type", async () => {
const tac = createSafeActionClient({
validationAdapter: zodAdapter(),
handleReturnedServerError: (e) => {
handleServerError: (e) => {
// disable server error logging for this test
throw e;
},
});
Expand Down
17 changes: 2 additions & 15 deletions packages/next-safe-action/src/action-builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,7 @@ export function actionBuilder<
handleBindArgsValidationErrorsShape: HandleBindArgsValidationErrorsShapeFn<BAS, CBAVE>;
metadataSchema: MetadataSchema;
metadata: MD;
handleServerErrorLog: NonNullable<SafeActionClientOpts<ServerError, MetadataSchema, any>["handleServerErrorLog"]>;
handleReturnedServerError: NonNullable<
SafeActionClientOpts<ServerError, MetadataSchema, any>["handleReturnedServerError"]
>;
handleServerError: NonNullable<SafeActionClientOpts<ServerError, MetadataSchema, any>["handleServerError"]>;
middlewareFns: MiddlewareFn<ServerError, any, any, any>[];
ctxType: Ctx;
throwValidationErrors: boolean;
Expand Down Expand Up @@ -250,7 +247,7 @@ export function actionBuilder<
// the default message.
const error = isError(e) ? e : new Error(DEFAULT_SERVER_ERROR_MESSAGE);
const returnedError = await Promise.resolve(
args.handleReturnedServerError(error, {
args.handleServerError(error, {
clientInput: clientInputs.at(-1), // pass raw client input
bindArgsClientInputs: bindArgsSchemas.length ? clientInputs.slice(0, -1) : [],
ctx: currentCtx,
Expand All @@ -259,16 +256,6 @@ export function actionBuilder<
);

middlewareResult.serverError = returnedError;

await Promise.resolve(
args.handleServerErrorLog(error, {
returnedError,
clientInput: clientInputs.at(-1), // pass raw client input
bindArgsClientInputs: bindArgsSchemas.length ? clientInputs.slice(0, -1) : [],
ctx: currentCtx,
metadata: args.metadata as MetadataSchema extends Schema ? Infer<MetadataSchema> : undefined,
})
);
}
}
};
Expand Down
26 changes: 8 additions & 18 deletions packages/next-safe-action/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,27 +40,17 @@ export const createSafeActionClient = <
>(
createOpts?: SafeActionClientOpts<ServerError, MetadataSchema, ODVES>
) => {
// If server log function is not provided, default to `console.error` for logging
// server error messages.
const handleServerErrorLog =
createOpts?.handleServerErrorLog ||
(((originalError: Error) => {
console.error("Action error:", originalError.message);
}) as unknown as NonNullable<SafeActionClientOpts<ServerError, MetadataSchema, ODVES>["handleServerErrorLog"]>);

// If `handleReturnedServerError` is provided, use it to handle server error
// messages returned on the client.
// Otherwise mask the error and use a generic message.
const handleReturnedServerError =
createOpts?.handleReturnedServerError ||
((() => DEFAULT_SERVER_ERROR_MESSAGE) as unknown as NonNullable<
SafeActionClientOpts<ServerError, MetadataSchema, ODVES>["handleReturnedServerError"]
>);
// If `handleServerError` is provided, use it, otherwise default to log to console and generic error message.
const handleServerError: NonNullable<SafeActionClientOpts<ServerError, MetadataSchema, ODVES>["handleServerError"]> =
createOpts?.handleServerError ||
((e) => {
console.error("Action error:", e.message);
return DEFAULT_SERVER_ERROR_MESSAGE as ServerError;
});

return new SafeActionClient({
middlewareFns: [async ({ next }) => next({ ctx: {} })],
handleServerErrorLog,
handleReturnedServerError,
handleServerError,
inputSchemaFn: undefined,
bindArgsSchemas: [],
outputSchema: undefined,
Expand Down
11 changes: 1 addition & 10 deletions packages/next-safe-action/src/index.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,16 +29,7 @@ export type SafeActionClientOpts<
> = {
validationAdapter?: ValidationAdapter;
defineMetadataSchema?: () => MetadataSchema;
handleReturnedServerError?: (
error: Error,
utils: ServerErrorFunctionUtils<MetadataSchema>
) => MaybePromise<ServerError>;
handleServerErrorLog?: (
originalError: Error,
utils: ServerErrorFunctionUtils<MetadataSchema> & {
returnedError: ServerError;
}
) => MaybePromise<void>;
handleServerError?: (error: Error, utils: ServerErrorFunctionUtils<MetadataSchema>) => MaybePromise<ServerError>;
throwValidationErrors?: boolean;
defaultValidationErrorsShape?: ODVES;
};
Expand Down
33 changes: 11 additions & 22 deletions packages/next-safe-action/src/safe-action-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,8 @@ export class SafeActionClient<
CVE = undefined,
const CBAVE = undefined,
> {
readonly #handleServerErrorLog: NonNullable<
SafeActionClientOpts<ServerError, MetadataSchema, ODVES>["handleServerErrorLog"]
>;
readonly #handleReturnedServerError: NonNullable<
SafeActionClientOpts<ServerError, MetadataSchema, ODVES>["handleReturnedServerError"]
readonly #handleServerError: NonNullable<
SafeActionClientOpts<ServerError, MetadataSchema, ODVES>["handleServerError"]
>;
readonly #middlewareFns: MiddlewareFn<ServerError, any, any, any>[];
readonly #metadataSchema: MetadataSchema;
Expand Down Expand Up @@ -65,13 +62,12 @@ export class SafeActionClient<
} & Required<
Pick<
SafeActionClientOpts<ServerError, MetadataSchema, ODVES>,
"handleReturnedServerError" | "handleServerErrorLog" | "defaultValidationErrorsShape" | "throwValidationErrors"
"handleServerError" | "defaultValidationErrorsShape" | "throwValidationErrors"
>
>
) {
this.#middlewareFns = opts.middlewareFns;
this.#handleServerErrorLog = opts.handleServerErrorLog;
this.#handleReturnedServerError = opts.handleReturnedServerError;
this.#handleServerError = opts.handleServerError;
this.#metadataSchema = opts.metadataSchema;
this.#metadata = opts.metadata;
this.#inputSchemaFn = (opts.inputSchemaFn ?? undefined) as ISF;
Expand All @@ -94,8 +90,7 @@ export class SafeActionClient<
use<NextCtx extends object>(middlewareFn: MiddlewareFn<ServerError, MD, Ctx, Ctx & NextCtx>) {
return new SafeActionClient({
middlewareFns: [...this.#middlewareFns, middlewareFn],
handleReturnedServerError: this.#handleReturnedServerError,
handleServerErrorLog: this.#handleServerErrorLog,
handleServerError: this.#handleServerError,
metadataSchema: this.#metadataSchema,
metadata: this.#metadata,
inputSchemaFn: this.#inputSchemaFn,
Expand All @@ -119,8 +114,7 @@ export class SafeActionClient<
metadata(data: MD) {
return new SafeActionClient({
middlewareFns: this.#middlewareFns,
handleReturnedServerError: this.#handleReturnedServerError,
handleServerErrorLog: this.#handleServerErrorLog,
handleServerError: this.#handleServerError,
metadataSchema: this.#metadataSchema,
metadata: data,
inputSchemaFn: this.#inputSchemaFn,
Expand Down Expand Up @@ -154,8 +148,7 @@ export class SafeActionClient<
) {
return new SafeActionClient({
middlewareFns: this.#middlewareFns,
handleReturnedServerError: this.#handleReturnedServerError,
handleServerErrorLog: this.#handleServerErrorLog,
handleServerError: this.#handleServerError,
metadataSchema: this.#metadataSchema,
metadata: this.#metadata,
// @ts-expect-error
Expand Down Expand Up @@ -196,8 +189,7 @@ export class SafeActionClient<
) {
return new SafeActionClient({
middlewareFns: this.#middlewareFns,
handleReturnedServerError: this.#handleReturnedServerError,
handleServerErrorLog: this.#handleServerErrorLog,
handleServerError: this.#handleServerError,
metadataSchema: this.#metadataSchema,
metadata: this.#metadata,
inputSchemaFn: this.#inputSchemaFn,
Expand All @@ -222,8 +214,7 @@ export class SafeActionClient<
outputSchema<OOS extends Schema>(dataSchema: OOS) {
return new SafeActionClient({
middlewareFns: this.#middlewareFns,
handleReturnedServerError: this.#handleReturnedServerError,
handleServerErrorLog: this.#handleServerErrorLog,
handleServerError: this.#handleServerError,
metadataSchema: this.#metadataSchema,
metadata: this.#metadata,
inputSchemaFn: this.#inputSchemaFn,
Expand All @@ -250,8 +241,7 @@ export class SafeActionClient<
utils?: SafeActionUtils<ServerError, MD, Ctx, IS, BAS, CVE, CBAVE, Data>
) {
return actionBuilder({
handleReturnedServerError: this.#handleReturnedServerError,
handleServerErrorLog: this.#handleServerErrorLog,
handleServerError: this.#handleServerError,
middlewareFns: this.#middlewareFns,
ctxType: this.#ctxType,
metadataSchema: this.#metadataSchema,
Expand Down Expand Up @@ -279,8 +269,7 @@ export class SafeActionClient<
utils?: SafeActionUtils<ServerError, MD, Ctx, IS, BAS, CVE, CBAVE, Data>
) {
return actionBuilder({
handleReturnedServerError: this.#handleReturnedServerError,
handleServerErrorLog: this.#handleServerErrorLog,
handleServerError: this.#handleServerError,
middlewareFns: this.#middlewareFns,
ctxType: this.#ctxType,
metadataSchema: this.#metadataSchema,
Expand Down
2 changes: 1 addition & 1 deletion website/docs/define-actions/action-result-object.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,4 @@ Here's how action result object is structured (all keys are optional):
- `data`: when execution is successful, what you returned in action's server code.
- `validationErrors`: when input data doesn't pass validation, an object that contains the validation errors. Can be customized using [`defaultValidationErrorsShape`](/docs/define-actions/create-the-client#defaultvalidationerrorsshape) initialization option and/or via [`handleValidationErrorsShape`function passed to `schema` method](/docs/define-actions/validation-errors#customize-validation-errors-format).
- `bindArgsValidationErrors`: when bound arguments don't pass validation, an object that contains the validation errors. Can be customized using [`defaultValidationErrorsShape`](/docs/define-actions/create-the-client#defaultvalidationerrorsshape) initialization option and/or via [`handleBindArgsValidationErrorsShape` function passed to `bindArgsSchemas` method](/docs/define-actions/validation-errors#customize-validation-errors-format).
- `serverError`: when execution fails, an error object that contains the error message, customizable by using the [`handleReturnedServerError`](/docs/define-actions/create-the-client#handlereturnedservererror) initialization function.
- `serverError`: when execution fails, an error object that contains the error message, customizable by using the [`handleServerError`](/docs/define-actions/create-the-client#handleservererror) initialization function.
Loading

0 comments on commit c900720

Please sign in to comment.