|
| 1 | +// |
| 2 | +// ignore_for_file: avoid_catches_without_on_clauses |
| 3 | + |
| 4 | +import 'dart:io'; |
| 5 | + |
| 6 | +import 'package:dart_frog/dart_frog.dart'; |
| 7 | +import 'package:ht_http_client/ht_http_client.dart'; |
| 8 | + |
| 9 | +/// Middleware that catches errors and converts them into |
| 10 | +/// standardized JSON responses. |
| 11 | +Middleware errorHandler() { |
| 12 | + return (handler) { |
| 13 | + return (context) async { |
| 14 | + try { |
| 15 | + // Attempt to execute the request handler |
| 16 | + final response = await handler(context); |
| 17 | + return response; |
| 18 | + } on HtHttpException catch (e, stackTrace) { |
| 19 | + // Handle specific HtHttpExceptions from the client/repository layers |
| 20 | + final statusCode = _mapExceptionToStatusCode(e); |
| 21 | + final errorCode = _mapExceptionToCodeString(e); |
| 22 | + print('HtHttpException Caught: $e\n$stackTrace'); // Log for debugging |
| 23 | + return Response.json( |
| 24 | + statusCode: statusCode, |
| 25 | + body: { |
| 26 | + 'error': { |
| 27 | + 'code': errorCode, |
| 28 | + 'message': e.message, |
| 29 | + }, |
| 30 | + }, |
| 31 | + ); |
| 32 | + } on FormatException catch (e, stackTrace) { |
| 33 | + // Handle data format/parsing errors (often indicates bad client input) |
| 34 | + print('FormatException Caught: $e\n$stackTrace'); // Log for debugging |
| 35 | + return Response.json( |
| 36 | + statusCode: HttpStatus.badRequest, // 400 |
| 37 | + body: { |
| 38 | + 'error': { |
| 39 | + 'code': 'INVALID_FORMAT', |
| 40 | + 'message': 'Invalid data format: ${e.message}', |
| 41 | + }, |
| 42 | + }, |
| 43 | + ); |
| 44 | + } catch (e, stackTrace) { |
| 45 | + // Handle any other unexpected errors |
| 46 | + print('Unhandled Exception Caught: $e\n$stackTrace'); |
| 47 | + return Response.json( |
| 48 | + statusCode: HttpStatus.internalServerError, // 500 |
| 49 | + body: { |
| 50 | + 'error': { |
| 51 | + 'code': 'INTERNAL_SERVER_ERROR', |
| 52 | + 'message': 'An unexpected internal server error occurred.', |
| 53 | + // Avoid leaking sensitive details in production responses |
| 54 | + // 'details': e.toString(), // Maybe include in dev mode only |
| 55 | + }, |
| 56 | + }, |
| 57 | + ); |
| 58 | + } |
| 59 | + }; |
| 60 | + }; |
| 61 | +} |
| 62 | + |
| 63 | +/// Maps HtHttpException subtypes to appropriate HTTP status codes. |
| 64 | +int _mapExceptionToStatusCode(HtHttpException exception) { |
| 65 | + return switch (exception) { |
| 66 | + BadRequestException() => HttpStatus.badRequest, // 400 |
| 67 | + UnauthorizedException() => HttpStatus.unauthorized, // 401 |
| 68 | + ForbiddenException() => HttpStatus.forbidden, // 403 |
| 69 | + NotFoundException() => HttpStatus.notFound, // 404 |
| 70 | + ServerException() => HttpStatus.internalServerError, // 500 |
| 71 | + NetworkException() => HttpStatus.serviceUnavailable, // 503 (or 500) |
| 72 | + UnknownException() => HttpStatus.internalServerError, // 500 |
| 73 | + }; |
| 74 | +} |
| 75 | + |
| 76 | +/// Maps HtHttpException subtypes to consistent error code strings. |
| 77 | +String _mapExceptionToCodeString(HtHttpException exception) { |
| 78 | + return switch (exception) { |
| 79 | + BadRequestException() => 'BAD_REQUEST', |
| 80 | + UnauthorizedException() => 'UNAUTHORIZED', |
| 81 | + ForbiddenException() => 'FORBIDDEN', |
| 82 | + NotFoundException() => 'NOT_FOUND', |
| 83 | + ServerException() => 'SERVER_ERROR', |
| 84 | + NetworkException() => 'NETWORK_ERROR', // Or 'SERVICE_UNAVAILABLE' |
| 85 | + UnknownException() => 'UNKNOWN_ERROR', |
| 86 | + }; |
| 87 | +} |
0 commit comments