From 660442d2f204c1e6060bb09a494fcda9368ce983 Mon Sep 17 00:00:00 2001 From: Omar Date: Fri, 17 Jul 2026 20:32:15 +0200 Subject: [PATCH] fix(backend): align domain error response envelope with Nest default shape MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit DomainExceptionFilter previously emitted { statusCode, code, message } while Nest's built-in HttpException emits { statusCode, message, error }. The two shapes coexisting across the same API surface meant clients had to branch on which endpoint they hit to know which field to read. Rename the filter's output field from 'code' to 'error' so every JSON error body has the same key layout regardless of whether it originated in the domain filter (fleet-management rule violations) or in a Nest exception (vehicle CRUD 404s, ValidationPipe failures, ...). The stored value is unchanged — it still carries the machine-readable domain code like VEHICLE_ALREADY_ASSIGNED. Swagger examples in vehicles / operators / fleet-management openapi.ts updated to reflect the new key. Operator dashboard is updated in the same commit — its api.ts parses body.error into ApiError.code, and the ApiErrorBody type renames the wire field. Internal ApiError.code stays as-is to avoid touching every call site. --- .../src/apis/filters/domain-exception.filter.ts | 7 ++----- apps/backend/src/apis/fleet-management/openapi.ts | 10 +++++----- apps/backend/src/apis/operators/openapi.ts | 2 +- apps/backend/src/apis/vehicles/openapi.ts | 4 ++-- apps/operator-dashboard/src/api.ts | 9 +++++++-- apps/operator-dashboard/src/types.ts | 2 +- 6 files changed, 18 insertions(+), 16 deletions(-) diff --git a/apps/backend/src/apis/filters/domain-exception.filter.ts b/apps/backend/src/apis/filters/domain-exception.filter.ts index 38545cc..10afbdc 100644 --- a/apps/backend/src/apis/filters/domain-exception.filter.ts +++ b/apps/backend/src/apis/filters/domain-exception.filter.ts @@ -5,10 +5,7 @@ import { HttpStatus, } from '@nestjs/common'; import { Response } from 'express'; -import { - DomainError, - DomainErrorKind, -} from '../../domain/shared/domain.error'; +import { DomainError, DomainErrorKind } from '../../domain/shared/domain.error'; /** * Generic HTTP mapping for domain errors. New DomainError subclasses just need @@ -36,7 +33,7 @@ export class DomainExceptionFilter implements ExceptionFilter { response.status(status).json({ statusCode: status, - code: exception.code, + error: exception.code, message: exception.message, }); } diff --git a/apps/backend/src/apis/fleet-management/openapi.ts b/apps/backend/src/apis/fleet-management/openapi.ts index 35a11b6..f18f6f8 100644 --- a/apps/backend/src/apis/fleet-management/openapi.ts +++ b/apps/backend/src/apis/fleet-management/openapi.ts @@ -27,30 +27,30 @@ export const releaseSuccessExample: Vehicle = { export const vehicleOfflineErrorExample = { statusCode: 409, - code: 'VEHICLE_OFFLINE', + error: 'VEHICLE_OFFLINE', message: 'Only online vehicles can be taken over', }; export const vehicleAlreadyAssignedErrorExample = { statusCode: 409, - code: 'VEHICLE_ALREADY_ASSIGNED', + error: 'VEHICLE_ALREADY_ASSIGNED', message: 'Another operator is holding this vehicle', }; export const operatorAlreadyHasVehicleErrorExample = { statusCode: 409, - code: 'OPERATOR_ALREADY_HAS_VEHICLE', + error: 'OPERATOR_ALREADY_HAS_VEHICLE', message: 'Operator is already holding a vehicle; release it first', }; export const vehicleNotHeldByOperatorErrorExample = { statusCode: 409, - code: 'VEHICLE_NOT_HELD_BY_OPERATOR', + error: 'VEHICLE_NOT_HELD_BY_OPERATOR', message: 'Operator is not holding this vehicle', }; export const notFoundErrorExample = { statusCode: 404, - code: 'VEHICLE_NOT_FOUND', + error: 'VEHICLE_NOT_FOUND', message: `Vehicle ${VEHICLE_ID} not found`, }; diff --git a/apps/backend/src/apis/operators/openapi.ts b/apps/backend/src/apis/operators/openapi.ts index 7dbd15b..15a5e7f 100644 --- a/apps/backend/src/apis/operators/openapi.ts +++ b/apps/backend/src/apis/operators/openapi.ts @@ -23,6 +23,6 @@ export const operatorListExample: Operator[] = [ export const operatorNotFoundExample = { statusCode: 404, - code: 'OPERATOR_NOT_FOUND', + error: 'OPERATOR_NOT_FOUND', message: `Operator ${OP_ID_A} not found`, }; diff --git a/apps/backend/src/apis/vehicles/openapi.ts b/apps/backend/src/apis/vehicles/openapi.ts index e9d58f6..9f6f259 100644 --- a/apps/backend/src/apis/vehicles/openapi.ts +++ b/apps/backend/src/apis/vehicles/openapi.ts @@ -39,12 +39,12 @@ export const setConnectivityOfflineExample = { status: 'offline' as const }; export const vehicleNotFoundExample = { statusCode: 404, - code: 'VEHICLE_NOT_FOUND', + error: 'VEHICLE_NOT_FOUND', message: `Vehicle ${SAMPLE_VEHICLE_ID} not found`, }; export const cannotOfflineAssignedExample = { statusCode: 409, - code: 'CANNOT_OFFLINE_ASSIGNED_VEHICLE', + error: 'CANNOT_OFFLINE_ASSIGNED_VEHICLE', message: 'release the vehicle first', }; diff --git a/apps/operator-dashboard/src/api.ts b/apps/operator-dashboard/src/api.ts index 3752966..50fcc40 100644 --- a/apps/operator-dashboard/src/api.ts +++ b/apps/operator-dashboard/src/api.ts @@ -1,4 +1,9 @@ -import { ApiError, type ApiErrorBody, type Operator, type Vehicle } from './types'; +import { + ApiError, + type ApiErrorBody, + type Operator, + type Vehicle, +} from './types'; const BASE_URL = import.meta.env.VITE_API_URL ?? 'http://localhost:3000'; @@ -17,7 +22,7 @@ async function request(path: string, init?: RequestInit): Promise { } throw new ApiError( body.message ?? `Request failed with status ${res.status}`, - body.code ?? 'UNKNOWN_ERROR', + body.error ?? 'UNKNOWN_ERROR', res.status, ); } diff --git a/apps/operator-dashboard/src/types.ts b/apps/operator-dashboard/src/types.ts index 0adbe7f..1732a73 100644 --- a/apps/operator-dashboard/src/types.ts +++ b/apps/operator-dashboard/src/types.ts @@ -13,7 +13,7 @@ export type Operator = { export type ApiErrorBody = { statusCode: number; - code: string; + error: string; message: string; };