import 'dart:convert'; import 'package:logging/logging.dart'; import 'package:shelf/shelf.dart'; final _log = Logger('ErrorHandler'); // ── ApiException ────────────────────────────────────────────────────── /// Typed exception that maps directly to an HTTP status code. class ApiException implements Exception { final int statusCode; final String message; final dynamic errors; const ApiException(this.statusCode, this.message, {this.errors}); // Factory constructors for common HTTP errors factory ApiException.badRequest(String message, {dynamic errors}) => ApiException(400, message, errors: errors); factory ApiException.unauthorized([String message = 'Unauthorized']) => ApiException(401, message); factory ApiException.forbidden([String message = 'Forbidden']) => ApiException(403, message); factory ApiException.notFound([String message = 'Resource not found']) => ApiException(404, message); factory ApiException.conflict([String message = 'Conflict']) => ApiException(409, message); factory ApiException.tooManyRequests( [String message = 'Too many requests']) => ApiException(429, message); @override String toString() => 'ApiException($statusCode): $message'; } // ── Middleware ───────────────────────────────────────────────────────── /// Catches all exceptions and returns a consistent JSON error response. Middleware errorHandlerMiddleware() { return (Handler innerHandler) { return (Request request) async { try { return await innerHandler(request); } on ApiException catch (e) { return _jsonError(e.statusCode, e.message, errors: e.errors); } catch (e, st) { _log.severe('Unhandled exception', e, st); return _jsonError(500, 'Internal server error'); } }; }; } Response _jsonError(int statusCode, String message, {dynamic errors}) { return Response( statusCode, body: jsonEncode({ 'success': false, 'message': message, if (errors != null) 'errors': errors, }), headers: {'Content-Type': 'application/json'}, ); }