Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: merge server error handling functions into handleServerError #257

Merged
merged 3 commits into from
Sep 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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