initial commit

This commit is contained in:
BeauTroll
2026-01-19 08:52:38 +01:00
commit 46907ca153
193 changed files with 35051 additions and 0 deletions

View File

@@ -0,0 +1,167 @@
# 18. Error Handling
## Unified Error Types
```typescript
// src/lib/errors.ts
export enum ErrorCode {
// Validation (400)
VALIDATION_ERROR = 'VALIDATION_ERROR',
INVALID_INPUT = 'INVALID_INPUT',
// Authentication (401)
UNAUTHORIZED = 'UNAUTHORIZED',
SESSION_EXPIRED = 'SESSION_EXPIRED',
// Authorization (403)
FORBIDDEN = 'FORBIDDEN',
INSUFFICIENT_PERMISSIONS = 'INSUFFICIENT_PERMISSIONS',
// Not Found (404)
NOT_FOUND = 'NOT_FOUND',
RESOURCE_NOT_FOUND = 'RESOURCE_NOT_FOUND',
// Conflict (409)
CONFLICT = 'CONFLICT',
DUPLICATE_ENTRY = 'DUPLICATE_ENTRY',
// External Service (502)
EXTERNAL_API_ERROR = 'EXTERNAL_API_ERROR',
DOFUSDB_ERROR = 'DOFUSDB_ERROR',
// Server (500)
INTERNAL_ERROR = 'INTERNAL_ERROR',
DATABASE_ERROR = 'DATABASE_ERROR',
}
export interface AppErrorData {
code: ErrorCode;
message: string;
details?: Record<string, unknown>;
field?: string;
}
export class AppError extends Error {
public readonly code: ErrorCode;
public readonly statusCode: number;
public readonly details?: Record<string, unknown>;
public readonly field?: string;
public readonly isOperational: boolean;
constructor(data: AppErrorData & { statusCode?: number }) {
super(data.message);
this.name = 'AppError';
this.code = data.code;
this.statusCode = data.statusCode ?? this.getDefaultStatusCode(data.code);
this.details = data.details;
this.field = data.field;
this.isOperational = true;
Error.captureStackTrace(this, this.constructor);
}
private getDefaultStatusCode(code: ErrorCode): number {
const statusMap: Record<ErrorCode, number> = {
[ErrorCode.VALIDATION_ERROR]: 400,
[ErrorCode.INVALID_INPUT]: 400,
[ErrorCode.UNAUTHORIZED]: 401,
[ErrorCode.SESSION_EXPIRED]: 401,
[ErrorCode.FORBIDDEN]: 403,
[ErrorCode.INSUFFICIENT_PERMISSIONS]: 403,
[ErrorCode.NOT_FOUND]: 404,
[ErrorCode.RESOURCE_NOT_FOUND]: 404,
[ErrorCode.CONFLICT]: 409,
[ErrorCode.DUPLICATE_ENTRY]: 409,
[ErrorCode.EXTERNAL_API_ERROR]: 502,
[ErrorCode.DOFUSDB_ERROR]: 502,
[ErrorCode.INTERNAL_ERROR]: 500,
[ErrorCode.DATABASE_ERROR]: 500,
};
return statusMap[code] ?? 500;
}
toJSON(): AppErrorData & { statusCode: number } {
return {
code: this.code,
message: this.message,
statusCode: this.statusCode,
details: this.details,
field: this.field,
};
}
}
```
## Backend Error Handler
```typescript
// src/lib/server/error-handler.ts
import { Prisma } from '@prisma/client';
import { AppError, ErrorCode } from '@/lib/errors';
export function handlePrismaError(error: unknown): AppError {
if (error instanceof Prisma.PrismaClientKnownRequestError) {
switch (error.code) {
case 'P2002':
return new AppError({
code: ErrorCode.DUPLICATE_ENTRY,
message: 'Cette entrée existe déjà',
details: { fields: error.meta?.target },
});
case 'P2025':
return new AppError({
code: ErrorCode.NOT_FOUND,
message: 'Ressource non trouvée',
});
default:
return new AppError({
code: ErrorCode.DATABASE_ERROR,
message: 'Erreur de base de données',
});
}
}
return new AppError({
code: ErrorCode.INTERNAL_ERROR,
message: 'Une erreur inattendue est survenue',
});
}
```
## Frontend Error Handler
```typescript
// src/lib/client/error-handler.ts
import { toast } from 'sonner';
import { AppError, ErrorCode } from '@/lib/errors';
const errorMessages: Record<ErrorCode, string> = {
[ErrorCode.VALIDATION_ERROR]: 'Données invalides',
[ErrorCode.UNAUTHORIZED]: 'Veuillez vous connecter',
[ErrorCode.NOT_FOUND]: 'Ressource non trouvée',
[ErrorCode.DUPLICATE_ENTRY]: 'Cette entrée existe déjà',
[ErrorCode.INTERNAL_ERROR]: 'Erreur serveur',
// ... other codes
};
export function handleError(error: unknown): void {
if (error instanceof AppError) {
const message = error.message || errorMessages[error.code];
if (error.statusCode >= 500) {
toast.error('Erreur serveur', { description: message });
} else {
toast.warning(message);
}
return;
}
console.error('Unexpected error:', error);
toast.error('Une erreur inattendue est survenue');
}
```
---