/**
* Error Mapper - Translates application errors to HTTP responses
*
* Maps custom error classes to appropriate HTTP status codes and formats
* error responses for consistent API error handling.
*/
import { ZodError } from "zod";
import {
ComposeOperationError,
HostOperationError,
SSHCommandError,
ValidationError,
} from "../utils/errors.js";
import { HostSecurityError, SSHArgSecurityError } from "../utils/path-security.js";
import type { ErrorCode, ErrorResponse, HTTPStatusCode } from "./types.js";
/**
* Maps error instances to appropriate HTTP status codes
*
* @param error - Error instance to map
* @returns HTTP status code (400, 502, or 500)
*
* @example
* mapErrorToStatus(new ValidationError(...)) // returns 400
* mapErrorToStatus(new HostOperationError(...)) // returns 502
* mapErrorToStatus(new Error(...)) // returns 500
*/
export function mapErrorToStatus(error: unknown): HTTPStatusCode {
// 400 Bad Request - Client input errors
if (
error instanceof ValidationError ||
error instanceof HostSecurityError ||
error instanceof SSHArgSecurityError ||
error instanceof ZodError
) {
return 400;
}
// 502 Bad Gateway - Remote host/service errors
if (
error instanceof HostOperationError ||
error instanceof SSHCommandError ||
error instanceof ComposeOperationError
) {
return 502;
}
// 500 Internal Server Error - Unknown/unexpected errors
return 500;
}
/**
* Gets error code string for classification
*
* @param error - Error instance to classify
* @returns Error code string for API response
*
* @example
* getErrorCode(new ValidationError(...)) // returns "VALIDATION_ERROR"
* getErrorCode(new HostSecurityError(...)) // returns "SECURITY_ERROR"
*/
export function getErrorCode(error: unknown): ErrorCode {
if (error instanceof ValidationError || error instanceof ZodError) {
return "VALIDATION_ERROR";
}
if (error instanceof HostSecurityError || error instanceof SSHArgSecurityError) {
return "SECURITY_ERROR";
}
if (error instanceof HostOperationError) {
return "HOST_OPERATION_ERROR";
}
if (error instanceof SSHCommandError) {
return "SSH_COMMAND_ERROR";
}
if (error instanceof ComposeOperationError) {
return "COMPOSE_OPERATION_ERROR";
}
return "INTERNAL_ERROR";
}
/**
* Formats error into structured JSON response
*
* @param error - Error to format
* @param requestId - Request ID for tracing
* @param includeStack - Whether to include stack trace (default: false)
* @returns Formatted error response object
*
* @example
* formatErrorResponse(new ValidationError(...), "req-123")
* // Returns: { error: { message: "...", code: "VALIDATION_ERROR", requestId: "req-123", details: {...} } }
*/
export function formatErrorResponse(
error: unknown,
requestId: string,
includeStack = false
): ErrorResponse {
const code = getErrorCode(error);
let message = "Unknown error";
let details: unknown;
let stack: string | undefined;
// Extract message and details based on error type
if (error instanceof ValidationError) {
message = error.message;
details = {
handler: error.handlerName,
issues: error.issues,
};
} else if (error instanceof ZodError) {
message = "Validation failed";
// Transform Zod errors into flat array of strings
details = {
issues: error.issues.map((err) => {
const path = err.path.join(".");
return path ? `${path}: ${err.message}` : err.message;
}),
};
} else if (error instanceof Error) {
message = error.message;
// Don't include details for security/operational errors (may contain sensitive info)
} else if (typeof error === "string") {
message = error;
}
// Include stack trace if requested and error has one
if (includeStack && error instanceof Error && error.stack) {
stack = error.stack;
}
const response: ErrorResponse = {
error: {
message,
code,
requestId,
},
};
if (details !== undefined) {
response.error.details = details;
}
if (stack !== undefined) {
response.error.stack = stack;
}
return response;
}