Skip to content

Commit

Permalink
fix: errors appear as json strings MAASENG-2888 lp#1819628 (#5359)
Browse files Browse the repository at this point in the history
  • Loading branch information
petermakowski authored Mar 20, 2024
1 parent cf895df commit a128d0a
Show file tree
Hide file tree
Showing 2 changed files with 82 additions and 32 deletions.
13 changes: 13 additions & 0 deletions src/app/utils/formatErrors.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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."
);
});
});
101 changes: 69 additions & 32 deletions src/app/utils/formatErrors.ts
Original file line number Diff line number Diff line change
@@ -1,53 +1,90 @@
import type { APIError } from "@/app/base/types";
import type { EventError } from "@/app/store/types/state";

const flattenErrors = <E>(errors: E): string | null => {
type FlattenedError = string | null;

const flattenErrors = <E>(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<string, unknown>,
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<string, unknown>,
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, ErrorFormatter> = {
string: parseJSONError,
object: parseObjectError,
};

export type ErrorType<E = null, I = any, K extends keyof I = any> =
| APIError<E>
| EventError<I, E, K>
| 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 = <E, I, K extends keyof I>(
errors?: ErrorType<E, I, K>,
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;
};

0 comments on commit a128d0a

Please sign in to comment.