From d2adf265e40d37fd283fa0dffa6b90145828f3b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Thu, 21 May 2026 19:27:20 +0200 Subject: [PATCH] refactor(api): extract buildJsonErrorResponse from apiError and returnAllZodIssues MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The W2 `returnAllZodIssues` helper duplicated the meta/headers handling from `apiError`: same `{headers, ...rest}` destructure, same `metaKeys` length check, same `...(headers ? {headers} : {})` spread on the constructor. Six lines that drift the moment one helper gains a feature (e.g. an `errorId` autoinject for Sentry) the other doesn't. Extract the shared body into `buildJsonErrorResponse(body, status, meta)`. Both `apiError` and `returnAllZodIssues` collapse to one-line constructions of the resource-specific body shape. Wire shape is byte-identical — the 7-case `api-response-zod` suite and every downstream `apiError` consumer still see the same envelopes. --- src/lib/api-response.ts | 65 +++++++++++++++++++++++------------------ 1 file changed, 36 insertions(+), 29 deletions(-) diff --git a/src/lib/api-response.ts b/src/lib/api-response.ts index 797d420b4..940e4ca64 100644 --- a/src/lib/api-response.ts +++ b/src/lib/api-response.ts @@ -44,23 +44,29 @@ export function sanitiseZodIssues( })); } -export function returnAllZodIssues( - error: ZodError, - status: number = 422, - meta?: { - errorCode?: string; - headers?: Record; - } & Record, +type ErrorMeta = { + errorCode?: string; + headers?: Record; +} & Record; + +/** + * Shared builder for every `{ data: null, error, ... }` JSON envelope. + * Strips `headers` from the meta passthrough (it lands on the + * NextResponse constructor, not in the JSON body) and omits the `meta` + * key entirely when no non-header fields remain — so the unchanged + * `{ data: null, error: }` envelope still serialises byte- + * identically when the caller passes no extras. + */ +function buildJsonErrorResponse( + body: Record, + status: number, + meta: ErrorMeta | undefined, ): NextResponse { const { headers, ...rest } = meta ?? {}; const metaKeys = Object.keys(rest); return NextResponse.json( { - data: null, - error: "Validation failed", - details: { - issues: sanitiseZodIssues(error.issues), - }, + ...body, ...(metaKeys.length > 0 ? { meta: rest } : {}), }, { @@ -70,6 +76,22 @@ export function returnAllZodIssues( ); } +export function returnAllZodIssues( + error: ZodError, + status: number = 422, + meta?: ErrorMeta, +): NextResponse { + return buildJsonErrorResponse( + { + data: null, + error: "Validation failed", + details: { issues: sanitiseZodIssues(error.issues) }, + }, + status, + meta, + ); +} + /** * `meta` is additive — clients that ignore it see the unchanged * `{ data: null, error: }` envelope. New callers can pass @@ -86,24 +108,9 @@ export function returnAllZodIssues( export function apiError( message: string, status = 400, - meta?: { - errorCode?: string; - headers?: Record; - } & Record, + meta?: ErrorMeta, ) { - const { headers, ...rest } = meta ?? {}; - const metaKeys = Object.keys(rest); - return NextResponse.json( - { - data: null, - error: message, - ...(metaKeys.length > 0 ? { meta: rest } : {}), - }, - { - status, - ...(headers ? { headers } : {}), - }, - ); + return buildJsonErrorResponse({ data: null, error: message }, status, meta); } /**