import 'dart:convert'; import 'package:shelf/shelf.dart'; import 'package:logging/logging.dart'; final _log = Logger('ErrorHandler'); /// Global error-handling middleware. /// /// Catches synchronous and asynchronous exceptions thrown by inner handlers /// and converts them to structured JSON error responses. Middleware errorHandler() { return (Handler innerHandler) { return (Request request) async { try { final response = await innerHandler(request); return response; } on ApiException catch (e) { _log.warning('API error: ${e.code} - ${e.message}'); return Response(e.statusCode, headers: {'content-type': 'application/json'}, body: jsonEncode({ 'status': 'error', 'error': { 'code': e.code, 'message': e.message, if (e.details != null) 'details': e.details, }, })); } on FormatException catch (e) { _log.warning('Bad request: $e'); return Response(400, headers: {'content-type': 'application/json'}, body: jsonEncode({ 'status': 'error', 'error': { 'code': 'BAD_REQUEST', 'message': 'Invalid request format: ${e.message}', }, })); } catch (e, st) { _log.severe('Unhandled exception', e, st); return Response.internalServerError( headers: {'content-type': 'application/json'}, body: jsonEncode({ 'status': 'error', 'error': { 'code': 'INTERNAL_ERROR', 'message': 'Internal server error', }, }), ); } }; }; } /// A typed exception that the error handler can inspect to produce a proper /// HTTP status and machine-readable error code. class ApiException implements Exception { final int statusCode; final String code; final String message; final dynamic details; ApiException(this.statusCode, this.code, this.message, {this.details}); factory ApiException.badRequest(String message, {dynamic details}) => ApiException(400, 'BAD_REQUEST', message, details: details); factory ApiException.unauthorized([String message = 'Unauthorized']) => ApiException(401, 'UNAUTHORIZED', message); factory ApiException.forbidden([String message = 'Forbidden']) => ApiException(403, 'FORBIDDEN', message); factory ApiException.notFound([String message = 'Not found']) => ApiException(404, 'NOT_FOUND', message); factory ApiException.conflict(String message) => ApiException(409, 'CONFLICT', message); factory ApiException.tooManyRequests([String message = 'Too many requests']) => ApiException(429, 'TOO_MANY_REQUESTS', message); factory ApiException.internal([String message = 'Internal server error']) => ApiException(500, 'INTERNAL_ERROR', message); @override String toString() => 'ApiException($statusCode, $code, $message)'; }