168 lines
4.3 KiB
Markdown
168 lines
4.3 KiB
Markdown
# 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');
|
|
}
|
|
```
|
|
|
|
---
|