@ministryofjustice/hmpps-digital-prison-reporting-frontend
Version:
The Digital Prison Reporting Frontend contains templates and code to help display data effectively in UI applications.
126 lines (123 loc) • 4.36 kB
JavaScript
import { ZodError } from 'zod';
import logger from '../logger.js';
import { AggregatedValidationError } from './AggregatedValidationError.js';
class ErrorHandler {
error;
constructor(error) {
this.error = error;
logger.error(`Error: ${JSON.stringify(error)}`);
}
formatError() {
return this.handleError();
}
handleError() {
const err = this.error;
// 1. Zod
if (err instanceof ZodError) {
const issues = err.issues.map(i => i.message).join('. ');
return {
status: 500,
userMessage: `Schema validation error: ${issues}`,
};
}
// 2. AggregatedValidationError
if (err instanceof AggregatedValidationError) {
return {
status: 500,
userMessage: [
err.message,
...err.details.map(detail => {
const issues = detail.issues.map(i => i.message).join('. ');
let prefix = '';
if (detail.type)
prefix += `Type: '${detail.type}'. `;
if (detail.id)
prefix += `ID: '${detail.id}'. `;
return `${prefix}Issues: ${issues}`;
}),
],
};
}
// 3. SuperAgent / HTTP errors
if (this.isSuperAgentError(err)) {
const transportStatus = typeof err.status === 'number' ? err.status : (err.response?.status ?? 500);
// 1. Prefer response.body if present
let payload = err.response?.body;
// 2. Otherwise try to parse text as JSON
if (payload === undefined && 'text' in err) {
payload = this.tryParseJson(err.text);
}
// 3. Extract actual payload (handles nested `data`)
const extracted = this.extractPayload(payload);
if (extracted) {
const { userMessage, developerMessage, moreInfo, status } = extracted;
return {
status: status ?? transportStatus,
...(userMessage !== undefined && { userMessage }),
...(developerMessage !== undefined && { developerMessage }),
...(moreInfo !== undefined && { moreInfo }),
};
}
// 4. Transport-level fallback
return {
status: transportStatus,
userMessage: err.message,
};
}
// 4. Generic JS error
if (typeof err === 'object' &&
err !== null &&
'message' in err &&
typeof err.message === 'string') {
return {
status: 500,
userMessage: err.message,
};
}
// 5. Unknown
return {
status: 500,
developerMessage: 'Unknown error',
};
}
/**
* Extracts error payload from:
* - direct payload
* - nested payload.data
*/
extractPayload(payload) {
if (this.isErrorPayload(payload))
return payload;
if (typeof payload === 'object' && payload !== null && 'data' in payload) {
const maybeData = payload.data;
if (this.isErrorPayload(maybeData)) {
return maybeData;
}
}
return undefined;
}
isSuperAgentError(error) {
if (typeof error !== 'object' || error === null)
return false;
const e = error;
return (typeof e['message'] === 'string' &&
(typeof e['status'] === 'number' || typeof e['text'] !== 'undefined' || typeof e['response'] === 'object'));
}
isErrorPayload(value) {
return (typeof value === 'object' &&
value !== null &&
('userMessage' in value || 'developerMessage' in value || 'moreInfo' in value || 'status' in value));
}
tryParseJson(value) {
if (typeof value !== 'string')
return undefined;
try {
return JSON.parse(value);
}
catch {
return undefined;
}
}
}
export { ErrorHandler, ErrorHandler as default };
//# sourceMappingURL=ErrorHandler.js.map