- Added serverless-offline plugin to package.json and serverless.yml for local development. - Implemented new showSuggestion function in host.yml with appropriate memory size and event configuration. - Removed deprecated getSuggestion function from minglaradmin.yml and updated related handlers. - Enhanced safeHandler to convert Prisma errors to ApiError automatically, improving error handling. - Introduced comprehensive Prisma error handling in ApiError.ts, mapping error codes to user-friendly messages and HTTP status codes.
265 lines
8.7 KiB
TypeScript
265 lines
8.7 KiB
TypeScript
import { Prisma } from '@prisma/client';
|
|
|
|
/**
|
|
* Prisma error code mappings to user-friendly messages and HTTP status codes
|
|
*/
|
|
const PRISMA_ERROR_CODES: Record<string, { statusCode: number; message: string }> = {
|
|
// Common errors (P1xxx)
|
|
P1000: { statusCode: 500, message: 'Authentication failed against database server' },
|
|
P1001: { statusCode: 503, message: 'Database server is not reachable' },
|
|
P1002: { statusCode: 504, message: 'Database server timed out' },
|
|
P1003: { statusCode: 500, message: 'Database does not exist' },
|
|
P1008: { statusCode: 504, message: 'Database operation timed out' },
|
|
P1009: { statusCode: 409, message: 'Database already exists' },
|
|
P1010: { statusCode: 403, message: 'User was denied access to the database' },
|
|
P1011: { statusCode: 500, message: 'Error opening a TLS connection' },
|
|
P1012: { statusCode: 500, message: 'Schema validation error' },
|
|
P1013: { statusCode: 400, message: 'Invalid database connection string' },
|
|
P1014: { statusCode: 500, message: 'Underlying model does not exist' },
|
|
P1015: { statusCode: 500, message: 'Database schema uses unsupported features' },
|
|
P1016: { statusCode: 400, message: 'Raw query has incorrect number of parameters' },
|
|
P1017: { statusCode: 503, message: 'Database server has closed the connection' },
|
|
|
|
// Prisma Client Query Engine errors (P2xxx)
|
|
P2000: { statusCode: 400, message: 'Value too long for column' },
|
|
P2001: { statusCode: 404, message: 'Record not found' },
|
|
P2002: { statusCode: 409, message: 'Unique constraint violation' },
|
|
P2003: { statusCode: 409, message: 'Foreign key constraint violation' },
|
|
P2004: { statusCode: 400, message: 'Database constraint violation' },
|
|
P2005: { statusCode: 400, message: 'Invalid value stored in database' },
|
|
P2006: { statusCode: 400, message: 'Invalid value provided' },
|
|
P2007: { statusCode: 400, message: 'Data validation error' },
|
|
P2008: { statusCode: 400, message: 'Failed to parse the query' },
|
|
P2009: { statusCode: 400, message: 'Failed to validate the query' },
|
|
P2010: { statusCode: 500, message: 'Raw query failed' },
|
|
P2011: { statusCode: 400, message: 'Null constraint violation' },
|
|
P2012: { statusCode: 400, message: 'Missing required value' },
|
|
P2013: { statusCode: 400, message: 'Missing required argument' },
|
|
P2014: { statusCode: 409, message: 'Required relation violation' },
|
|
P2015: { statusCode: 404, message: 'Related record not found' },
|
|
P2016: { statusCode: 400, message: 'Query interpretation error' },
|
|
P2017: { statusCode: 400, message: 'Records for relation not connected' },
|
|
P2018: { statusCode: 404, message: 'Required connected records not found' },
|
|
P2019: { statusCode: 400, message: 'Input error' },
|
|
P2020: { statusCode: 400, message: 'Value out of range' },
|
|
P2021: { statusCode: 500, message: 'Table does not exist' },
|
|
P2022: { statusCode: 500, message: 'Column does not exist' },
|
|
P2023: { statusCode: 500, message: 'Inconsistent column data' },
|
|
P2024: { statusCode: 503, message: 'Connection pool timeout' },
|
|
P2025: { statusCode: 404, message: 'Record not found' },
|
|
P2026: { statusCode: 400, message: 'Unsupported database feature used in query' },
|
|
P2027: { statusCode: 500, message: 'Multiple database errors occurred' },
|
|
P2028: { statusCode: 500, message: 'Transaction API error' },
|
|
P2029: { statusCode: 400, message: 'Query parameter limit exceeded' },
|
|
P2030: { statusCode: 400, message: 'Fulltext index not found' },
|
|
P2031: { statusCode: 500, message: 'MongoDB requires replica set' },
|
|
P2033: { statusCode: 400, message: 'Number does not fit in 64 bit signed integer' },
|
|
P2034: { statusCode: 409, message: 'Transaction failed due to write conflict or deadlock' },
|
|
P2035: { statusCode: 500, message: 'Database assertion violation' },
|
|
P2036: { statusCode: 500, message: 'External connector error' },
|
|
P2037: { statusCode: 503, message: 'Too many database connections opened' },
|
|
};
|
|
|
|
interface PrismaErrorMeta {
|
|
target?: string[];
|
|
field_name?: string;
|
|
model_name?: string;
|
|
argument_name?: string;
|
|
constraint?: string;
|
|
cause?: string;
|
|
[key: string]: unknown;
|
|
}
|
|
|
|
class ApiError<T = unknown> extends Error {
|
|
statusCode: number;
|
|
data: T | null;
|
|
message: string;
|
|
success: boolean;
|
|
errors: Array<Error>;
|
|
isOperational: boolean;
|
|
stack?: string;
|
|
code?: string;
|
|
meta?: PrismaErrorMeta;
|
|
|
|
constructor(
|
|
statusCode: number,
|
|
message: string = 'Something went wrong',
|
|
errors: Array<Error> = [],
|
|
isOperational: boolean = true,
|
|
stack?: string,
|
|
code?: string,
|
|
meta?: PrismaErrorMeta
|
|
) {
|
|
super(message);
|
|
this.statusCode = statusCode;
|
|
this.data = null;
|
|
this.message = message;
|
|
this.success = false;
|
|
this.errors = errors;
|
|
this.isOperational = isOperational;
|
|
this.code = code;
|
|
this.meta = meta;
|
|
|
|
if (stack) {
|
|
this.stack = stack;
|
|
} else {
|
|
Error.captureStackTrace(this, this.constructor);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Creates an ApiError from a Prisma error
|
|
* Handles all Prisma error types: PrismaClientKnownRequestError, PrismaClientUnknownRequestError,
|
|
* PrismaClientRustPanicError, PrismaClientInitializationError, PrismaClientValidationError
|
|
*/
|
|
static fromPrismaError(error: unknown): ApiError {
|
|
// Handle PrismaClientKnownRequestError
|
|
if (error instanceof Prisma.PrismaClientKnownRequestError) {
|
|
const errorInfo = PRISMA_ERROR_CODES[error.code] || {
|
|
statusCode: 500,
|
|
message: 'Database operation failed',
|
|
};
|
|
|
|
let message = errorInfo.message;
|
|
const meta = error.meta as PrismaErrorMeta | undefined;
|
|
|
|
// Provide more specific messages based on error code and meta
|
|
switch (error.code) {
|
|
case 'P2002': {
|
|
const target = meta?.target;
|
|
if (target && Array.isArray(target)) {
|
|
message = `Unique constraint violation on field(s): ${target.join(', ')}`;
|
|
}
|
|
break;
|
|
}
|
|
case 'P2003': {
|
|
const fieldName = meta?.field_name;
|
|
if (fieldName) {
|
|
message = `Foreign key constraint failed on field: ${fieldName}`;
|
|
}
|
|
break;
|
|
}
|
|
case 'P2025': {
|
|
const cause = meta?.cause;
|
|
if (cause) {
|
|
message = `Record not found: ${cause}`;
|
|
}
|
|
break;
|
|
}
|
|
case 'P2011': {
|
|
const constraint = meta?.constraint;
|
|
if (constraint) {
|
|
message = `Null constraint violation on: ${constraint}`;
|
|
}
|
|
break;
|
|
}
|
|
case 'P2014': {
|
|
const modelName = meta?.model_name;
|
|
if (modelName) {
|
|
message = `Required relation violation on model: ${modelName}`;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
return new ApiError(
|
|
errorInfo.statusCode,
|
|
message,
|
|
[error],
|
|
true,
|
|
error.stack,
|
|
error.code,
|
|
meta
|
|
);
|
|
}
|
|
|
|
// Handle PrismaClientUnknownRequestError
|
|
if (error instanceof Prisma.PrismaClientUnknownRequestError) {
|
|
return new ApiError(
|
|
500,
|
|
'An unknown database error occurred',
|
|
[error],
|
|
true,
|
|
error.stack,
|
|
'UNKNOWN_REQUEST_ERROR'
|
|
);
|
|
}
|
|
|
|
// Handle PrismaClientRustPanicError
|
|
if (error instanceof Prisma.PrismaClientRustPanicError) {
|
|
return new ApiError(
|
|
500,
|
|
'A critical database error occurred. Please try again later.',
|
|
[error],
|
|
false,
|
|
error.stack,
|
|
'RUST_PANIC_ERROR'
|
|
);
|
|
}
|
|
|
|
// Handle PrismaClientInitializationError
|
|
if (error instanceof Prisma.PrismaClientInitializationError) {
|
|
const errorInfo = error.errorCode
|
|
? PRISMA_ERROR_CODES[error.errorCode] || { statusCode: 500, message: 'Database initialization failed' }
|
|
: { statusCode: 500, message: 'Database initialization failed' };
|
|
|
|
return new ApiError(
|
|
errorInfo.statusCode,
|
|
errorInfo.message,
|
|
[error],
|
|
false,
|
|
error.stack,
|
|
error.errorCode || 'INITIALIZATION_ERROR'
|
|
);
|
|
}
|
|
|
|
// Handle PrismaClientValidationError
|
|
if (error instanceof Prisma.PrismaClientValidationError) {
|
|
return new ApiError(
|
|
400,
|
|
'Invalid data provided for database operation',
|
|
[error],
|
|
true,
|
|
error.stack,
|
|
'VALIDATION_ERROR'
|
|
);
|
|
}
|
|
|
|
// Fallback for any other error
|
|
if (error instanceof Error) {
|
|
return new ApiError(500, error.message, [error], true, error.stack);
|
|
}
|
|
|
|
return new ApiError(500, 'An unexpected error occurred');
|
|
}
|
|
|
|
/**
|
|
* Check if an error is a Prisma error
|
|
*/
|
|
static isPrismaError(error: unknown): boolean {
|
|
return (
|
|
error instanceof Prisma.PrismaClientKnownRequestError ||
|
|
error instanceof Prisma.PrismaClientUnknownRequestError ||
|
|
error instanceof Prisma.PrismaClientRustPanicError ||
|
|
error instanceof Prisma.PrismaClientInitializationError ||
|
|
error instanceof Prisma.PrismaClientValidationError
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Get a user-friendly message for a Prisma error code
|
|
*/
|
|
static getPrismaErrorMessage(code: string): string {
|
|
return PRISMA_ERROR_CODES[code]?.message || 'Database operation failed';
|
|
}
|
|
|
|
/**
|
|
* Get HTTP status code for a Prisma error code
|
|
*/
|
|
static getPrismaErrorStatusCode(code: string): number {
|
|
return PRISMA_ERROR_CODES[code]?.statusCode || 500;
|
|
}
|
|
}
|
|
|
|
export default ApiError;
|