# Contributing Guide ## Error Handling Convention ### Overview All application-layer error handling uses **domain exceptions** from `@modules/shared/domain/domain-exception`. Never import exception classes from `@nestjs/common` in handlers — use the project's own domain exceptions instead. The `GlobalExceptionFilter` catches all exceptions and normalizes them into a consistent JSON response with structured error codes. ### Domain Exceptions | Exception | HTTP Status | When to use | |---|---|---| | `NotFoundException(entity, id?)` | 404 | Entity not found in database | | `ValidationException(message, details?)` | 400 | Invalid input, business rule violation, value object creation failure | | `ConflictException(message)` | 409 | Duplicate resource, idempotency violation | | `UnauthorizedException(message?)` | 401 | Invalid/expired credentials or tokens | | `ForbiddenException(message?)` | 403 | Authenticated but not authorized for the action | Import from: ```typescript import { NotFoundException, ValidationException } from '@modules/shared/domain/domain-exception'; ``` ### Patterns by Layer #### Command/Query Handlers Handlers throw domain exceptions directly. No try-catch wrapping needed — the `GlobalExceptionFilter` handles uncaught exceptions. ```typescript // Good: domain exception with entity context const user = await this.userRepo.findById(id); if (!user) throw new NotFoundException('User', id); // Good: Result pattern from value objects const phoneResult = Phone.create(command.phone); if (phoneResult.isErr) { throw new ValidationException(phoneResult.unwrapErr()); } const phone = phoneResult.unwrap(); // Good: business rule validation if (subscription.status === 'CANCELLED') { throw new ValidationException('Subscription da bi huy'); } ``` #### Controllers Controllers are thin delegation layers — they dispatch to the command/query bus and return the result. No error handling needed at the controller level. #### Domain Services / Value Objects Use the `Result` pattern from `@modules/shared/domain/result`: ```typescript static create(value: string): Result { if (!isValid(value)) return Result.err('So dien thoai khong hop le'); return Result.ok(new Phone({ value })); } ``` Handlers consume `Result` by checking `.isErr` and throwing a `ValidationException`. ### What NOT to Do ```typescript // Bad: NestJS built-in exceptions (missing errorCode in response) import { NotFoundException } from '@nestjs/common'; // Bad: object-style exceptions (inconsistent with domain exception API) throw new NotFoundException({ code: ErrorCode.NOT_FOUND, message: '...' }); // Bad: generic try-catch swallowing infrastructure errors as domain errors try { await this.prisma.plan.findFirst({ where: { tier } }); } catch { throw new NotFoundException('Plan not found'); // hides DB connection errors } // Bad: returning Result from handlers to controllers // Handlers should unwrap Result and throw on error ``` ### Repository Return Types All repository read methods must return explicitly typed DTOs — never `Promise` or `PaginatedResult`. Define read DTOs in the domain layer alongside the repository interface. See `listing-read.dto.ts` for the canonical example.