chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
/**
|
||||
* Combo API error helper (T-22.b).
|
||||
*
|
||||
* Standardizes the 400/404/409 response shape for `/api/combos/*` routes.
|
||||
* Every failure surfaces a stable machine-readable `code` token (from
|
||||
* `src/shared/constants/errorCodes.ts`), a human-readable `message`, an
|
||||
* optional `details` payload, and the current `requestId` for log
|
||||
* correlation. The prior shape returned `{ error: <string|object> }` with
|
||||
* no `code` field, which forced clients to string-match English error
|
||||
* messages. See `plans/2026-06-23-omniroute-v3.8.34-deep-audit.md` (Bug #3).
|
||||
*
|
||||
* Usage:
|
||||
* return comboErrorResponse("COMBO_002", 400, { issues: validation.issues });
|
||||
* return comboErrorResponse("COMBO_005", 400, { reason: "cycle-detected" });
|
||||
*
|
||||
* The response is a plain `Response` (not `NextResponse.json`) so it can
|
||||
* be used from both Next.js route handlers and server-side callers. The
|
||||
* `x-request-id` header is also attached for downstream log correlation.
|
||||
*/
|
||||
|
||||
import { ERROR_CODES } from "@/shared/constants/errorCodes";
|
||||
import {
|
||||
attachRequestIdToResponse,
|
||||
getRequestId,
|
||||
} from "@/shared/utils/requestId";
|
||||
|
||||
export type ComboErrorCode =
|
||||
| "COMBO_001" // request body is not valid JSON
|
||||
| "COMBO_002" // zod schema failure
|
||||
| "COMBO_003" // composite tier config invalid
|
||||
| "COMBO_004" // name collision
|
||||
| "COMBO_005" // DAG cycle / depth overflow
|
||||
| "COMBO_006" // managed by Quota Share (409)
|
||||
| "COMBO_007" // not found (404)
|
||||
| "VALID_001" // generic invalid body
|
||||
| "VALID_002" // missing required field
|
||||
| "INTERNAL_001"; // fallback
|
||||
|
||||
export interface ComboErrorBody {
|
||||
error: {
|
||||
code: ComboErrorCode;
|
||||
message: string;
|
||||
category: string;
|
||||
details?: unknown;
|
||||
requestId?: string;
|
||||
};
|
||||
}
|
||||
|
||||
export function buildComboErrorBody(
|
||||
code: ComboErrorCode,
|
||||
details?: unknown
|
||||
): ComboErrorBody {
|
||||
const def = ERROR_CODES[code] ?? ERROR_CODES.INTERNAL_001;
|
||||
const requestId = getRequestId();
|
||||
return {
|
||||
error: {
|
||||
code: def.code as ComboErrorCode,
|
||||
message: def.message,
|
||||
category: def.category,
|
||||
...(details !== undefined ? { details } : {}),
|
||||
...(requestId ? { requestId } : {}),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function comboErrorResponse(
|
||||
code: ComboErrorCode,
|
||||
status?: number,
|
||||
details?: unknown,
|
||||
request?: Request
|
||||
): Response {
|
||||
const def = ERROR_CODES[code] ?? ERROR_CODES.INTERNAL_001;
|
||||
const httpStatus = status ?? def.httpStatus;
|
||||
const body = buildComboErrorBody(code, details);
|
||||
const response = Response.json(body, { status: httpStatus });
|
||||
// Attach x-request-id header for downstream consumers. If a request is
|
||||
// passed, prefer to derive its id (works outside withRequestId scope);
|
||||
// otherwise the response body already carries requestId when available.
|
||||
return request ? attachRequestIdToResponse(request, response) : response;
|
||||
}
|
||||
Reference in New Issue
Block a user