Skip to content

Commit

Permalink
chore: add general server action error
Browse files Browse the repository at this point in the history
  • Loading branch information
matejfalat committed May 3, 2024
1 parent c7d8b9a commit df4b261
Show file tree
Hide file tree
Showing 2 changed files with 62 additions and 5 deletions.
7 changes: 7 additions & 0 deletions src/lib/types/error.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,10 @@ export class ZodFormError extends ZodError {
this.name = 'ZodFormError'
}
}

export class ServerError extends Error {
constructor(message: string, options?: ErrorOptions) {
super(message, options)
this.name = 'ServerError'
}
}
60 changes: 55 additions & 5 deletions src/lib/utils/common/error.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,30 @@
import {ZodError, ZodIssue} from 'zod'

import {FormError, FormErrorData, ZodFormError} from '@/lib/types/error'
import {
FormError,
FormErrorData,
ServerError,
ZodFormError,
} from '@/lib/types/error'

export const API_ERROR_FLAG = 'api-error'
export const API_FORM_ERROR_FLAG = 'api-form-error'
export const API_ZOD_ERROR_FLAG = 'api-zod-error'

// General server action error, to be displayed as a toast
export type ApiError = {
type: typeof API_ERROR_FLAG
message: string
}

// General server action form error, to be handled by the form
export type ApiFormError<Data extends FormErrorData = undefined> = {
type: typeof API_FORM_ERROR_FLAG
message: string
data?: Data
}

// Server action zod validation error, FE may choose to handle it by the form or display as a toast
export type ApiZodError = {
type: typeof API_ZOD_ERROR_FLAG
issues: ZodIssue[]
Expand All @@ -25,7 +39,7 @@ export const getApiFormError = <T extends FormErrorData>(
data,
})

const isApiFormError = <T extends FormErrorData>(
export const isApiFormError = <T extends FormErrorData>(
obj: unknown,
): obj is ApiFormError<T> =>
typeof obj === 'object' &&
Expand All @@ -38,12 +52,44 @@ export const getApiZodError = (error: ZodError): ApiZodError => ({
issues: error.issues,
})

const isApiZodError = (obj: unknown): obj is ApiZodError =>
export const isApiZodError = (obj: unknown): obj is ApiZodError =>
typeof obj === 'object' &&
obj !== null &&
'type' in obj &&
obj.type === API_ZOD_ERROR_FLAG

export const getApiError = (message: string): ApiError => ({
type: API_ERROR_FLAG,
message,
})

export const isApiError = (obj: unknown): obj is ApiError =>
typeof obj === 'object' &&
obj !== null &&
'type' in obj &&
obj.type === API_ERROR_FLAG

export const serializeServerErrors =
<Args extends unknown[], Result extends object | null>(
action: (...args: Args) => Promise<Result>,
) =>
async (...args: Args) => {
try {
return await action(...args)
} catch (error) {
if (error instanceof ZodError) {
return getApiZodError(error)
}
if (error instanceof FormError) {
return getApiFormError(error.message, error.data)
}
if (error instanceof ServerError) {
return getApiError(error.message)
}
throw error
}
}

/**
* Function to parse server action return data and throw an error if it's an error object.
*
Expand All @@ -55,9 +101,13 @@ export const handleApiErrors = <
Result extends object,
ErrorData extends FormErrorData,
>(
result: Result | ApiFormError<ErrorData> | ApiZodError,
result: Result | ApiFormError<ErrorData> | ApiZodError | ApiError,
shouldFormHandleZodErrors?: boolean,
): Result => {
if (isApiError(result)) {
throw new ServerError(result.message)
}

if (isApiFormError(result)) {
throw new FormError(result.message, result.data)
}
Expand Down Expand Up @@ -86,7 +136,7 @@ export const withApiErrorHandler =
>(
action: (
...args: Args
) => Promise<Result | ApiFormError<ErrorData> | ApiZodError>,
) => Promise<Result | ApiFormError<ErrorData> | ApiZodError | ApiError>,
shouldFormHandleZodErrors?: boolean,
) =>
async (...args: Args) => {
Expand Down

0 comments on commit df4b261

Please sign in to comment.