- init fragmenté : lit target/path dans la query (corps en repli) (#1) - erreurs client interrompu / disque plein / fichier partiel absent mappées en 400/507/404 sans webhook d'échec pour les interruptions (#2) - nettoyage des sessions expirées et des .part orphelins sécurisé, log des unhandledRejection (#2) - plafond de sessions porté à 256 pour l'usage multi-fenêtres (#3) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
79 lines
2.6 KiB
TypeScript
79 lines
2.6 KiB
TypeScript
/**
|
|
* Hiérarchie d'erreurs de l'application.
|
|
*
|
|
* Toutes dérivent de `AppError`, qui porte un `statusCode` HTTP. Le gestionnaire
|
|
* d'erreurs Fastify (`src/error-handler.ts`) s'appuie dessus pour formater la réponse.
|
|
* Les messages ne doivent jamais contenir de donnée sensible (jeton, chemin absolu...).
|
|
*/
|
|
export abstract class AppError extends Error {
|
|
public readonly statusCode: number;
|
|
|
|
protected constructor(message: string, statusCode: number, options?: { cause?: unknown }) {
|
|
super(message, options);
|
|
this.name = this.constructor.name;
|
|
this.statusCode = statusCode;
|
|
}
|
|
}
|
|
|
|
/** Configuration invalide au démarrage (fichier JSON absent, illisible ou incohérent). */
|
|
export class ConfigError extends AppError {
|
|
public constructor(message: string, options?: { cause?: unknown }) {
|
|
super(message, 500, options);
|
|
}
|
|
}
|
|
|
|
/** Découverte OIDC impossible au démarrage (issuer injoignable ou réponse invalide). */
|
|
export class OidcDiscoveryError extends AppError {
|
|
public constructor(message: string, options?: { cause?: unknown }) {
|
|
super(message, 500, options);
|
|
}
|
|
}
|
|
|
|
/** Jeton d'accès absent, mal formé, expiré ou invalide. */
|
|
export class UnauthorizedError extends AppError {
|
|
public constructor(message = "Jeton d'accès absent ou invalide") {
|
|
super(message, 401);
|
|
}
|
|
}
|
|
|
|
/** Requête mal formée (fichier manquant, type non autorisé...). */
|
|
export class ValidationError extends AppError {
|
|
public constructor(message: string) {
|
|
super(message, 400);
|
|
}
|
|
}
|
|
|
|
/** Fichier au-dessus de la taille maximale autorisée. */
|
|
export class PayloadTooLargeError extends AppError {
|
|
public constructor(message = 'Le fichier dépasse la taille maximale autorisée') {
|
|
super(message, 413);
|
|
}
|
|
}
|
|
|
|
/** Session d'upload introuvable ou expirée. */
|
|
export class UploadSessionNotFoundError extends AppError {
|
|
public constructor(message = "Session d'upload inconnue ou expirée") {
|
|
super(message, 404);
|
|
}
|
|
}
|
|
|
|
/** Trop de sessions d'upload simultanées. */
|
|
export class TooManyUploadsError extends AppError {
|
|
public constructor(message = "Trop d'uploads en cours, réessayez plus tard") {
|
|
super(message, 429);
|
|
}
|
|
}
|
|
|
|
/** Le client a interrompu la requête (annulation, pause, coupure réseau) avant la fin du corps. */
|
|
export class ClientAbortedError extends AppError {
|
|
public constructor(message = 'Requête interrompue par le client') {
|
|
super(message, 400);
|
|
}
|
|
}
|
|
|
|
/** Plus d'espace disque disponible sur la cible de dépôt. */
|
|
export class InsufficientStorageError extends AppError {
|
|
public constructor(message = 'Espace de stockage insuffisant sur le serveur') {
|
|
super(message, 507);
|
|
}
|
|
}
|