Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 | 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 496x 496x 496x 2x 496x 2x 2x 2x 2x 2x 18x 2x 2x 2x 2x 2x 2x 2x 2x 2x 37x 2x 2x 2x 2x 2x 2x 3x 2x 2x 2x 2x 2x 20x 20x 20x 20x 9x 2x 2x 2x 2x 2x 2x 2x 2x 2x 3x 2x 2x 2x 2x 2x 2x 2x 2x 3x 2x 2x 2x 202x 2x 2x 2x 2x 2x 7x 2x 2x 25x 25x 6x 6x 6x 6x 2x 4x 4x 4x 4x 2x 2x 3x 3x 2x 192x 192x 192x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 1x 1x 3x 3x 2x 2x 2x 2x 3x 3x 211x 3x 8x 8x 8x 8x 2x 265x 54x 2x 2x 211x 49x 49x 2x 2x 49x 2x 2x 49x 2x 12x 2x 26x 2x 2x 2x 7x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 162x 2x 2x 2x 2x 163x 265x 2x | // src/services/db/errors.db.ts
import type { Logger } from 'pino';
import { DatabaseError as ProcessingDatabaseError } from '../processingErrors';
/**
* Base class for custom repository-level errors to ensure they have a status property.
*/
export class RepositoryError 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 RepositoryError {
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 RepositoryError {
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 RepositoryError {
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 RepositoryError {
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 RepositoryError {
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 RepositoryError {
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 RepositoryError {
constructor(message = 'The requested resource was not found.') {
super(message, 404); // 404 Not Found
}
}
/**
* Thrown when the user does not have permission to access the resource.
*/
export class ForbiddenError extends RepositoryError {
constructor(message = 'Access denied.') {
super(message, 403); // 403 Forbidden
this.name = 'ForbiddenError';
}
}
/**
* 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 RepositoryError {
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;
}
/**
* A type guard to check if an error object is a PostgreSQL error with a code.
*/
function isPostgresError(
error: unknown,
): error is { code: string; constraint?: string; detail?: string } {
return typeof error === 'object' && error !== null && 'code' in error;
}
/**
* 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 RepositoryError) {
throw error;
}
if (isPostgresError(error)) {
const { code, constraint, detail } = error;
const enhancedLogContext = { err: error, code, constraint, detail, ...logContext };
// Log the detailed error first
logger.error(enhancedLogContext, logMessage);
// Now, throw the appropriate custom error
switch (code) {
case '23505': // unique_violation
throw new UniqueConstraintError(options.uniqueMessage);
case '23503': // foreign_key_violation
throw new ForeignKeyConstraintError(options.fkMessage);
case '23502': // not_null_violation
throw new NotNullConstraintError(options.notNullMessage);
case '23514': // check_violation
throw new CheckConstraintError(options.checkMessage);
case '22P02': // invalid_text_representation
throw new InvalidTextRepresentationError(options.invalidTextMessage);
case '22003': // numeric_value_out_of_range
throw new NumericValueOutOfRangeError(options.numericOutOfRangeMessage);
default:
// If it's a PG error but not one we handle specifically, fall through to the generic error.
break;
}
} else {
// Log the error if it wasn't a recognized Postgres error
logger.error({ err: error, ...logContext }, logMessage);
}
// Fallback generic error
// Use the consistent DatabaseError from the processing errors module for the fallback.
const errorMessage = options.defaultMessage || `Failed to perform operation on ${options.entityName || 'database'}.`;
throw new ProcessingDatabaseError(errorMessage);
}
|