@beignet/core
Version:
Core framework primitives for Beignet
139 lines (127 loc) • 3.63 kB
text/typescript
/**
* Framework-agnostic error mapping utilities for @beignet/core/server
*/
import {
createErrorResponseBody,
type ErrorResponseBody,
} from "../../errors/index.js";
import type { AppEnvironment } from "../health.js";
import { getRequestIdFromContext } from "./utils.js";
/**
* Re-export `AppEnvironment` for convenience.
*/
export type { AppEnvironment } from "../health.js";
/**
* Framework-neutral response produced by an error mapper.
*/
export interface ErrorMappingResult {
/**
* HTTP status code.
*/
status: number;
/**
* Response body.
*/
body: unknown;
/**
* Response headers.
*/
headers?: Record<string, string>;
}
/**
* Error mapping configuration.
*/
export interface ErrorMappingConfig<Ctx> {
/** Custom error mapper function. */
mapErrorToResponse?: (err: unknown, ctx: Ctx) => ErrorMappingResult;
/** Include stack traces in error responses. Defaults to true in dev/test. */
includeStackInResponse?: boolean;
/** Application environment. */
env?: AppEnvironment;
}
/**
* Create default error response body
*/
function createDefaultErrorBody(
err: unknown,
includeStack: boolean,
requestId?: string,
): ErrorResponseBody {
return createErrorResponseBody({
code: "INTERNAL_SERVER_ERROR",
message: "Internal server error",
requestId,
details:
includeStack && err instanceof Error
? {
error: {
message: err.message,
stack: err.stack,
},
}
: undefined,
});
}
/**
* Default error mapping function that handles unknown errors and converts them
* to a standard error response format.
*
* **Important:** This function does NOT handle AppError instances from @beignet/core/errors.
* AppError is handled separately in the router's error handling flow before reaching
* this function. This function is only called for truly unexpected errors that bypass normal
* error handling (e.g., unhandled exceptions, infrastructure errors).
*
* This function:
* 1. First tries the custom mapErrorToResponse if provided
* 2. Falls back to a default 500 error response
* 3. Optionally includes stack traces in development/test environments
*
* @param err - The error that was thrown (excluding AppError instances)
* @param ctx - The request context
* @param config - Error mapping configuration
* @returns An error mapping result with status, body, and optional headers
*
* @example
* ```ts
* const errorConfig = {
* mapErrorToResponse: (err, ctx) => ({
* status: 500,
* body: {
* code: "INTERNAL_SERVER_ERROR",
* message: "Custom error",
* requestId: ctx.requestId,
* },
* }),
* includeStackInResponse: true,
* env: "development",
* };
*
* const result = defaultMapErrorToResponse(error, ctx, errorConfig);
* ```
*/
export function defaultMapErrorToResponse<Ctx>(
err: unknown,
ctx: Ctx,
config: ErrorMappingConfig<Ctx>,
): ErrorMappingResult {
// First, try the user's custom error handler
if (config.mapErrorToResponse) {
try {
return config.mapErrorToResponse(err, ctx);
} catch {
// Fall through to default error response below
}
}
// Determine if stack traces should be included
const includeStack =
config.includeStackInResponse ??
(config.env === "development" || config.env === "test");
// Default error response
const requestId = getRequestIdFromContext(ctx);
const body = createDefaultErrorBody(err, includeStack, requestId);
return {
status: 500,
body,
headers: { "Content-Type": "application/json" },
};
}