diff --git a/src/app/utils/formatErrors.test.ts b/src/app/utils/formatErrors.test.ts index e5b81ddc90..67597ad7bc 100644 --- a/src/app/utils/formatErrors.test.ts +++ b/src/app/utils/formatErrors.test.ts @@ -38,4 +38,17 @@ describe("formatErrors", () => { const typeError = new TypeError("Failed to fetch"); expect(formatErrors(typeError)).toEqual("Failed to fetch"); }); + + it("correctly formats a generic Error", () => { + const error = new Error("Something went wrong."); + expect(formatErrors(error)).toEqual("Something went wrong."); + }); + + it("correctly formats a JSON string error", () => { + const jsonError = + '{"__all__": ["The primary rack controller must be up and running to set a secondary rack controller."]}'; + expect(formatErrors(jsonError)).toEqual( + "The primary rack controller must be up and running to set a secondary rack controller." + ); + }); }); diff --git a/src/app/utils/formatErrors.ts b/src/app/utils/formatErrors.ts index 14a9608902..2a1e6117e4 100644 --- a/src/app/utils/formatErrors.ts +++ b/src/app/utils/formatErrors.ts @@ -1,53 +1,90 @@ import type { APIError } from "@/app/base/types"; import type { EventError } from "@/app/store/types/state"; -const flattenErrors = (errors: E): string | null => { +type FlattenedError = string | null; + +const flattenErrors = (errors: E): FlattenedError => { if (Array.isArray(errors)) { return errors.join(" "); } - if (typeof errors === "string") { - return errors; + return typeof errors === "string" ? errors : null; +}; + +const parseJSONError = (jsonError: string): FlattenedError => { + try { + const parsedError = JSON.parse(jsonError); + if (typeof parsedError === "object") { + const errorEntries = Object.entries(parsedError); + const isAllError = + errorEntries.length === 1 && errorEntries[0][0] === "__all__"; + return isAllError + ? flattenErrors(errorEntries[0][1]) + : errorEntries + .map(([key, value]) => `${key}: ${flattenErrors(value)}`) + .join(" "); + } + return flattenErrors(parsedError); + } catch { + return jsonError; + } +}; + +const formatObjectError = ( + errors: Record, + errorKey?: string +): FlattenedError => { + if (errorKey && errorKey in errors) { + return flattenErrors(errors[errorKey]); + } + const errorEntries = Object.entries(errors); + return errorEntries.length > 0 + ? errorEntries + .map(([key, value]) => `${key}: ${flattenErrors(value)}`) + .join(" ") + : null; +}; + +const parseObjectError = ( + errors: Record, + errorKey?: string +): FlattenedError => { + if (Array.isArray(errors)) { + return errors.join(" "); + } + if (errors instanceof Error) { + return errors.message; + } + if (typeof errors === "object" && errors !== null) { + return formatObjectError(errors, errorKey); } return null; }; -/** - * Format errors of different types to a single string. - * - * @param errors - the errors string/array/object - * @returns error message - */ +type ErrorFormatter = (errors: any, errorKey?: string) => FlattenedError; +const errorTypeFormatters: Record = { + string: parseJSONError, + object: parseObjectError, +}; export type ErrorType = | APIError | EventError | Error; +/** + * Formats errors of different types into a single string. + * @param errors - The errors to format, which can be a string, array, object, or JSON string. + * @param errorKey - The optional key to extract the error from if the errors are an object. + * @returns The formatted error message or null if no errors are provided. + */ export const formatErrors = ( errors?: ErrorType, errorKey?: string -): string | null => { - let errorMessage: string | null = null; - if (errors) { - if (Array.isArray(errors)) { - errorMessage = errors.join(" "); - } else if (typeof errors === "object") { - if (errorKey && errorKey in errors) { - errorMessage = flattenErrors(errors[errorKey as keyof typeof errors]); - } else { - const errorEntries = Object.entries(errors); - if (errorEntries.length > 0) { - errorMessage = Object.entries(errors) - .map(([key, value]) => `${key}: ${flattenErrors(value)}`) - .join(" "); - } else if (errors instanceof Error) { - errorMessage = errors?.message; - } - } - } else if (typeof errors === "string") { - errorMessage = errors; - } +): FlattenedError => { + if (!errors) { + return null; } - - return errorMessage; + const errorType = typeof errors; + const formatErrors = errorTypeFormatters[errorType]; + return formatErrors ? formatErrors(errors, errorKey) : null; };