Some checks failed
Deploy to Test Environment / deploy-to-test (push) Failing after 41s
164 lines
5.0 KiB
TypeScript
164 lines
5.0 KiB
TypeScript
// src/services/db/errors.db.ts
|
|
import type { Logger } from 'pino';
|
|
|
|
/**
|
|
* Base class for custom database errors to ensure they have a status property.
|
|
*/
|
|
export class DatabaseError extends Error {
|
|
public status: number;
|
|
|
|
constructor(message: string, status: number) {
|
|
super(message);
|
|
this.name = this.constructor.name;
|
|
this.status = status;
|
|
// This is necessary to make `instanceof` work correctly with transpiled TS classes
|
|
Object.setPrototypeOf(this, new.target.prototype);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Thrown when a unique constraint is violated (e.g., trying to register an existing email).
|
|
* Corresponds to PostgreSQL error code '23505'.
|
|
*/
|
|
export class UniqueConstraintError extends DatabaseError {
|
|
constructor(message = 'The record already exists.') {
|
|
super(message, 409); // 409 Conflict
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Thrown when a foreign key constraint is violated (e.g., trying to reference a non-existent record).
|
|
* Corresponds to PostgreSQL error code '23503'.
|
|
*/
|
|
export class ForeignKeyConstraintError extends DatabaseError {
|
|
constructor(message = 'The referenced record does not exist.') {
|
|
super(message, 400); // 400 Bad Request
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Thrown when a 'not null' constraint is violated.
|
|
* Corresponds to PostgreSQL error code '23502'.
|
|
*/
|
|
export class NotNullConstraintError extends DatabaseError {
|
|
constructor(message = 'A required field was left null.') {
|
|
super(message, 400); // 400 Bad Request
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Thrown when a 'check' constraint is violated.
|
|
* Corresponds to PostgreSQL error code '23514'.
|
|
*/
|
|
export class CheckConstraintError extends DatabaseError {
|
|
constructor(message = 'A check constraint was violated.') {
|
|
super(message, 400); // 400 Bad Request
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Thrown when a value has an invalid text representation for its data type (e.g., 'abc' for an integer).
|
|
* Corresponds to PostgreSQL error code '22P02'.
|
|
*/
|
|
export class InvalidTextRepresentationError extends DatabaseError {
|
|
constructor(message = 'A value has an invalid format for its data type.') {
|
|
super(message, 400); // 400 Bad Request
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Thrown when a numeric value is out of range for its data type (e.g., too large for an integer).
|
|
* Corresponds to PostgreSQL error code '22003'.
|
|
*/
|
|
export class NumericValueOutOfRangeError extends DatabaseError {
|
|
constructor(message = 'A numeric value is out of the allowed range.') {
|
|
super(message, 400); // 400 Bad Request
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Thrown when a specific record is not found in the database.
|
|
*/
|
|
export class NotFoundError extends DatabaseError {
|
|
constructor(message = 'The requested resource was not found.') {
|
|
super(message, 404); // 404 Not Found
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Defines the structure for a single validation issue, often from a library like Zod.
|
|
*/
|
|
export interface ValidationIssue {
|
|
path: (string | number)[];
|
|
message: string;
|
|
[key: string]: unknown; // Allow other properties that might exist on the error object
|
|
}
|
|
|
|
/**
|
|
* Thrown when request validation fails (e.g., missing body fields or invalid params).
|
|
*/
|
|
export class ValidationError extends DatabaseError {
|
|
public validationErrors: ValidationIssue[];
|
|
|
|
constructor(errors: ValidationIssue[], message = 'The request data is invalid.') {
|
|
super(message, 400); // 400 Bad Request
|
|
this.name = 'ValidationError';
|
|
this.validationErrors = errors;
|
|
}
|
|
}
|
|
|
|
export class FileUploadError extends Error {
|
|
public status = 400;
|
|
constructor(message: string) {
|
|
super(message);
|
|
this.name = 'FileUploadError';
|
|
}
|
|
}
|
|
|
|
export interface HandleDbErrorOptions {
|
|
entityName?: string;
|
|
uniqueMessage?: string;
|
|
fkMessage?: string;
|
|
notNullMessage?: string;
|
|
checkMessage?: string;
|
|
invalidTextMessage?: string;
|
|
numericOutOfRangeMessage?: string;
|
|
defaultMessage?: string;
|
|
}
|
|
|
|
/**
|
|
* Centralized error handler for database repositories.
|
|
* Logs the error and throws appropriate custom errors based on PostgreSQL error codes.
|
|
*/
|
|
export function handleDbError(
|
|
error: unknown,
|
|
logger: Logger,
|
|
logMessage: string,
|
|
logContext: Record<string, unknown>,
|
|
options: HandleDbErrorOptions = {},
|
|
): never {
|
|
// If it's already a known domain error (like NotFoundError thrown manually), rethrow it.
|
|
if (error instanceof DatabaseError) {
|
|
throw error;
|
|
}
|
|
|
|
// Log the raw error
|
|
logger.error({ err: error, ...logContext }, logMessage);
|
|
|
|
if (error instanceof Error && 'code' in error) {
|
|
const code = (error as any).code;
|
|
|
|
if (code === '23505') throw new UniqueConstraintError(options.uniqueMessage);
|
|
if (code === '23503') throw new ForeignKeyConstraintError(options.fkMessage);
|
|
if (code === '23502') throw new NotNullConstraintError(options.notNullMessage);
|
|
if (code === '23514') throw new CheckConstraintError(options.checkMessage);
|
|
if (code === '22P02') throw new InvalidTextRepresentationError(options.invalidTextMessage);
|
|
if (code === '22003') throw new NumericValueOutOfRangeError(options.numericOutOfRangeMessage);
|
|
}
|
|
|
|
// Fallback generic error
|
|
throw new Error(
|
|
options.defaultMessage || `Failed to perform operation on ${options.entityName || 'database'}.`,
|
|
);
|
|
}
|