feat(shared): add error handling & structured logging strategy
- Global exception filter with consistent error response format - Domain exceptions (NotFoundException, ValidationException, etc.) - Error codes enum for domain-specific error identification - Correlation ID middleware for request tracing - Request/response logging middleware with structured JSON - PII masking in logs (emails, phone numbers, sensitive fields) - Enhanced LoggerService with pino formatters and ISO timestamps - Tests for exception filter, domain exceptions, and PII masker Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
56
apps/api/src/modules/shared/domain/domain-exception.ts
Normal file
56
apps/api/src/modules/shared/domain/domain-exception.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import { HttpException, HttpStatus } from '@nestjs/common';
|
||||
import { ErrorCode } from './error-codes';
|
||||
|
||||
export interface ErrorResponseBody {
|
||||
statusCode: number;
|
||||
errorCode: ErrorCode;
|
||||
message: string;
|
||||
details?: Record<string, unknown>;
|
||||
correlationId?: string;
|
||||
timestamp: string;
|
||||
}
|
||||
|
||||
export class DomainException extends HttpException {
|
||||
constructor(
|
||||
public readonly errorCode: ErrorCode,
|
||||
message: string,
|
||||
statusCode: HttpStatus = HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
public readonly details?: Record<string, unknown>,
|
||||
) {
|
||||
super(message, statusCode);
|
||||
}
|
||||
}
|
||||
|
||||
export class NotFoundException extends DomainException {
|
||||
constructor(entity: string, id?: string) {
|
||||
super(
|
||||
ErrorCode.NOT_FOUND,
|
||||
id ? `${entity} with id '${id}' not found` : `${entity} not found`,
|
||||
HttpStatus.NOT_FOUND,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export class ValidationException extends DomainException {
|
||||
constructor(message: string, details?: Record<string, unknown>) {
|
||||
super(ErrorCode.VALIDATION_FAILED, message, HttpStatus.BAD_REQUEST, details);
|
||||
}
|
||||
}
|
||||
|
||||
export class ConflictException extends DomainException {
|
||||
constructor(message: string) {
|
||||
super(ErrorCode.CONFLICT, message, HttpStatus.CONFLICT);
|
||||
}
|
||||
}
|
||||
|
||||
export class UnauthorizedException extends DomainException {
|
||||
constructor(message = 'Unauthorized') {
|
||||
super(ErrorCode.UNAUTHORIZED, message, HttpStatus.UNAUTHORIZED);
|
||||
}
|
||||
}
|
||||
|
||||
export class ForbiddenException extends DomainException {
|
||||
constructor(message = 'Forbidden') {
|
||||
super(ErrorCode.FORBIDDEN, message, HttpStatus.FORBIDDEN);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user