/** * Map domain / HTTP errors to consistent JSON responses. */ import type { FastifyReply } from "fastify"; import { FeishuReadinessError } from "../connections/feishuReadiness.js"; import { ProviderReadinessError } from "../connections/providerReadiness.js"; import { HttpError, sendError } from "./auth/guards.js"; export async function handleRouteError(reply: FastifyReply, err: unknown): Promise { if (err instanceof HttpError) { await sendError(reply, err.statusCode, err.code, err.message); return; } if (err instanceof ProviderReadinessError) { await sendError( reply, err.code === "provider_readiness_unreachable" ? 502 : 400, err.code === "provider_readiness_unreachable" ? "provider_unavailable" : "provider_credential_rejected", err.message, ); return; } if (err instanceof FeishuReadinessError) { await sendError( reply, err.code === "feishu_readiness_unreachable" ? 502 : 400, err.code === "feishu_readiness_unreachable" ? "feishu_unavailable" : "feishu_credential_rejected", err.message, ); return; } if (err instanceof Error) { const mapped = mapDomainError(err.message); if (mapped !== null) { await sendError(reply, mapped.statusCode, mapped.code, mapped.message); return; } await sendError(reply, 500, "internal_error", "internal error"); return; } await sendError(reply, 500, "internal_error", "internal error"); } function mapDomainError(message: string): { statusCode: number; code: string; message: string } | null { const lower = message.toLowerCase(); if (lower.includes("provider credential readiness check could not reach provider")) { return { statusCode: 502, code: "provider_unavailable", message }; } if (lower.includes("provider credential readiness check failed")) { return { statusCode: 400, code: "provider_credential_rejected", message }; } if (lower.includes("feishu application readiness check could not reach provider")) { return { statusCode: 502, code: "feishu_unavailable", message }; } if (lower.includes("feishu application readiness check") || lower.includes("feishu application credentials were rejected")) { return { statusCode: 400, code: "feishu_credential_rejected", message }; } if (lower.includes("not found")) { return { statusCode: 404, code: "not_found", message }; } if ( lower.includes("requires") || lower.includes("forbidden") || lower.includes("cannot") || lower.includes("not an active member") || lower.includes("refused") ) { return { statusCode: 403, code: "forbidden", message }; } if ( lower.includes("is required") || lower.includes("already") || lower.includes("invalid") || lower.includes("accepts only") ) { return { statusCode: 400, code: "bad_request", message }; } return null; }