mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-10-12 14:34:16 +00:00
Co-authored-by: Pablo F.G <pablo.fernandez@prowler.com>
90 lines
2.5 KiB
TypeScript
90 lines
2.5 KiB
TypeScript
/**
|
|
* One error object from a JSON:API error body. `detail` is not guaranteed: the
|
|
* organization endpoints put the message under the offending field's own key and
|
|
* let the pointer stop at the containing attribute —
|
|
* `{"service_account_key": "…", "source": {"pointer": "/data/attributes/secret"}}`.
|
|
*/
|
|
export interface ApiErrorObject {
|
|
detail?: string;
|
|
source?: { pointer?: string };
|
|
code?: string;
|
|
status?: string;
|
|
[field: string]: unknown;
|
|
}
|
|
|
|
interface ErrorResult {
|
|
error?: string;
|
|
errors?: ApiErrorObject[];
|
|
}
|
|
|
|
/** Keys carrying error metadata rather than a field's message. */
|
|
const ERROR_META_KEYS = new Set([
|
|
"detail",
|
|
"source",
|
|
"code",
|
|
"status",
|
|
"title",
|
|
]);
|
|
|
|
function humanizeFieldName(field: string): string {
|
|
const words = field.replace(/_/g, " ");
|
|
return `${words.charAt(0).toUpperCase()}${words.slice(1)}`;
|
|
}
|
|
|
|
function findFieldMessage(
|
|
error: ApiErrorObject,
|
|
): { field: string; message: string } | null {
|
|
for (const [field, value] of Object.entries(error)) {
|
|
if (ERROR_META_KEYS.has(field)) continue;
|
|
if (typeof value === "string" && value.trim()) {
|
|
return { field, message: value.trim() };
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Readable text for a single server error: `detail` when present, otherwise the
|
|
* field-keyed message prefixed with its field — in that shape the key is the only
|
|
* thing naming the input the message is about.
|
|
*/
|
|
export function describeApiError(error: ApiErrorObject): string | null {
|
|
if (typeof error.detail === "string" && error.detail.trim()) {
|
|
return error.detail.trim();
|
|
}
|
|
|
|
const fieldMessage = findFieldMessage(error);
|
|
if (!fieldMessage) {
|
|
return null;
|
|
}
|
|
|
|
return `${humanizeFieldName(fieldMessage.field)}: ${fieldMessage.message}`;
|
|
}
|
|
|
|
/**
|
|
* The field names an error mentions — pointer segments plus its own keys — so a
|
|
* form field matches whichever of the two shapes the error arrived in.
|
|
*/
|
|
export function apiErrorFieldNames(error: ApiErrorObject): string {
|
|
const pointer = error.source?.pointer ?? "";
|
|
const keys = Object.keys(error).filter((key) => !ERROR_META_KEYS.has(key));
|
|
|
|
return `${pointer} ${keys.join(" ")}`;
|
|
}
|
|
|
|
export function extractErrorMessage(
|
|
response: unknown,
|
|
fallback: string,
|
|
): string {
|
|
if (!response || typeof response !== "object") {
|
|
return fallback;
|
|
}
|
|
|
|
const responseRecord = response as ErrorResult;
|
|
const firstError = responseRecord.errors?.[0];
|
|
const detailedError = firstError ? describeApiError(firstError) : null;
|
|
|
|
return detailedError || responseRecord.error || fallback;
|
|
}
|