import app from '@adonisjs/core/services/app' import { type HttpContext, ExceptionHandler } from '@adonisjs/core/http' /** * Exception handler API JSON-only. Normalise toutes les erreurs vers la * shape `{ errors: [{ code, message, field? }] }` documentée dans * backend.md §6. * * Conversions : * - PG 23505 (unique violation) → 422 `duplicate` avec field extrait * - E_INVALID_CREDENTIALS → 401 `invalid_credentials` * - Vine validation errors → 422 (déjà géré par Adonis, on relaie) * - Exception custom avec code & status → propage tel quel sous shape errors * - Reste → fallback super.handle() */ export default class HttpExceptionHandler extends ExceptionHandler { protected debug = !app.inProduction async handle(error: unknown, ctx: HttpContext) { if (!isObject(error)) return super.handle(error, ctx) // Postgres unique violation → 422 propre (pas un 500 avec stack pg-protocol). if (error.code === '23505') { const detail = typeof error.detail === 'string' ? error.detail : '' const fieldMatch = detail.match(/Key \(([^)]+)\)=/) const field = fieldMatch?.[1]?.split(',')[0]?.trim() ctx.response.status(422) return ctx.response.json({ errors: [ { code: 'duplicate', message: 'Cette valeur existe déjà.', field: field ?? undefined, }, ], }) } // Adonis auth — mauvais credentials. Le default est 400, on veut 401. if (error.code === 'E_INVALID_CREDENTIALS') { ctx.response.status(401) return ctx.response.json({ errors: [ { code: 'invalid_credentials', message: 'Email ou mot de passe incorrect', }, ], }) } // Vine — validation errors. Adonis sort déjà des messages structurés, // on les relaie en `errors[]`. if (error.code === 'E_VALIDATION_ERROR' && Array.isArray(error.messages)) { ctx.response.status(422) return ctx.response.json({ errors: error.messages.map((m) => ({ code: 'validation_failed', message: typeof m === 'object' && m && 'message' in m ? String(m.message) : '', field: typeof m === 'object' && m && 'field' in m ? String(m.field) : undefined, rule: typeof m === 'object' && m && 'rule' in m ? String(m.rule) : undefined, })), }) } // Custom Exception levée par les controllers : on a `status` + `code` // + `message`. On les passe en shape `errors[]`. if ( typeof error.status === 'number' && typeof error.code === 'string' && typeof error.message === 'string' && error.status >= 400 && error.status < 600 ) { ctx.response.status(error.status) return ctx.response.json({ errors: [ { code: error.code, message: error.message, }, ], }) } return super.handle(error, ctx) } async report(error: unknown, ctx: HttpContext) { return super.report(error, ctx) } } function isObject(v: unknown): v is Record { return v !== null && typeof v === 'object' }