11import { CopilotApiError , MessagableError , StatusableError } from '@/types/CopilotApiError'
22import APIError from '@api/core/exceptions/api'
3- import { PrismaClientKnownRequestError } from '@prisma/client/runtime/library'
43import httpStatus from 'http-status'
54import { NextRequest , NextResponse } from 'next/server'
65import { ZodError , ZodFormattedError } from 'zod'
@@ -13,10 +12,45 @@ type ErrorResponse = {
1312 status : number
1413}
1514
15+ type PrismaKnownRequestError = {
16+ code : string
17+ meta ?: Record < string , unknown >
18+ name ?: string
19+ }
20+
21+ const PRISMA_KNOWN_REQUEST_ERROR_NAME = 'PrismaClientKnownRequestError'
22+
23+ const getConstructorName = ( constructor : unknown ) => {
24+ if ( typeof constructor === 'function' ) {
25+ return constructor . name
26+ }
27+
28+ if ( ! constructor || typeof constructor !== 'object' ) {
29+ return undefined
30+ }
31+
32+ const name = ( constructor as { name ?: unknown } ) . name
33+ return typeof name === 'string' ? name : undefined
34+ }
35+
36+ const isPrismaKnownRequestError = ( error : unknown ) : error is PrismaKnownRequestError => {
37+ if ( ! error || typeof error !== 'object' ) {
38+ return false
39+ }
40+
41+ const candidate = error as Partial < PrismaKnownRequestError >
42+ const constructorName = getConstructorName ( candidate . constructor )
43+
44+ return (
45+ typeof candidate . code === 'string' &&
46+ ( candidate . name === PRISMA_KNOWN_REQUEST_ERROR_NAME || constructorName === PRISMA_KNOWN_REQUEST_ERROR_NAME )
47+ )
48+ }
49+
1650// P2023 ("Inconsistent column data") covers more than UUIDs (e.g. enum mismatches), so
1751// only treat it as not-found when the meta message points at a malformed UUID — otherwise
1852// it stays an unclassified error that logs, rather than being silently hidden as a 404.
19- const isInvalidUuidError = ( error : PrismaClientKnownRequestError ) => {
53+ const isInvalidUuidError = ( error : PrismaKnownRequestError ) => {
2054 if ( error . code === 'P2010' && error . meta ?. code === '22P02' ) {
2155 return true
2256 }
@@ -25,14 +59,21 @@ const isInvalidUuidError = (error: PrismaClientKnownRequestError) => {
2559 return error . code === 'P2023' && typeof metaMessage === 'string' && metaMessage . toLowerCase ( ) . includes ( 'uuid' )
2660}
2761
28- const getPrismaKnownRequestErrorResponse = ( error : PrismaClientKnownRequestError ) : ErrorResponse | null => {
62+ const getPrismaKnownRequestErrorResponse = ( error : PrismaKnownRequestError ) : ErrorResponse | null => {
2963 if ( error . code === 'P2025' || isInvalidUuidError ( error ) ) {
3064 return {
3165 status : httpStatus . NOT_FOUND ,
3266 message : 'The requested resource was not found' ,
3367 }
3468 }
3569
70+ if ( error . code === 'P2002' ) {
71+ return {
72+ status : httpStatus . CONFLICT ,
73+ message : 'A resource with these values already exists' ,
74+ }
75+ }
76+
3677 return null
3778}
3879
@@ -68,15 +109,15 @@ const normalizeError = (error: unknown): ErrorResponse => {
68109 }
69110 }
70111
71- if ( error instanceof PrismaClientKnownRequestError ) {
112+ if ( isPrismaKnownRequestError ( error ) ) {
72113 return getPrismaKnownRequestErrorResponse ( error ) || defaultResponse
73114 }
74115
75116 return defaultResponse
76117}
77118
78119const isExpectedPrismaError = ( error : unknown ) =>
79- error instanceof PrismaClientKnownRequestError && getPrismaKnownRequestErrorResponse ( error ) !== null
120+ isPrismaKnownRequestError ( error ) && getPrismaKnownRequestErrorResponse ( error ) !== null
80121
81122const isExpectedClientError = ( error : unknown ) => {
82123 return (
0 commit comments