44import 'dart:io' ;
55
66import 'package:dart_frog/dart_frog.dart' ;
7+ import 'package:ht_api/src/config/environment_config.dart' ;
78import 'package:ht_shared/ht_shared.dart' ;
89import 'package:json_annotation/json_annotation.dart' ;
910
@@ -19,54 +20,38 @@ Middleware errorHandler() {
1920 } on HtHttpException catch (e, stackTrace) {
2021 // Handle specific HtHttpExceptions from the client/repository layers
2122 final statusCode = _mapExceptionToStatusCode (e);
22- final errorCode = _mapExceptionToCodeString (e);
2323 print ('HtHttpException Caught: $e \n $stackTrace ' ); // Log for debugging
24- return Response . json (
24+ return _jsonErrorResponse (
2525 statusCode: statusCode,
26- body: {
27- 'error' : {'code' : errorCode, 'message' : e.message},
28- },
26+ exception: e,
27+ context: context,
2928 );
3029 } on CheckedFromJsonException catch (e, stackTrace) {
3130 // Handle json_serializable validation errors. These are client errors.
3231 final field = e.key ?? 'unknown' ;
3332 final message = 'Invalid request body: Field "$field " has an '
3433 'invalid value or is missing. ${e .message }' ;
3534 print ('CheckedFromJsonException Caught: $e \n $stackTrace ' );
36- return Response . json (
35+ return _jsonErrorResponse (
3736 statusCode: HttpStatus .badRequest, // 400
38- body: {
39- 'error' : {
40- 'code' : 'invalidField' ,
41- 'message' : message,
42- },
43- },
37+ exception: InvalidInputException (message),
38+ context: context,
4439 );
4540 } on FormatException catch (e, stackTrace) {
4641 // Handle data format/parsing errors (often indicates bad client input)
4742 print ('FormatException Caught: $e \n $stackTrace ' ); // Log for debugging
48- return Response . json (
43+ return _jsonErrorResponse (
4944 statusCode: HttpStatus .badRequest, // 400
50- body: {
51- 'error' : {
52- 'code' : 'invalidFormat' ,
53- 'message' : 'Invalid data format: ${e .message }' ,
54- },
55- },
45+ exception: InvalidInputException ('Invalid data format: ${e .message }' ),
46+ context: context,
5647 );
5748 } catch (e, stackTrace) {
5849 // Handle any other unexpected errors
5950 print ('Unhandled Exception Caught: $e \n $stackTrace ' );
60- return Response . json (
51+ return _jsonErrorResponse (
6152 statusCode: HttpStatus .internalServerError, // 500
62- body: {
63- 'error' : {
64- 'code' : 'internalServerError' ,
65- 'message' : 'An unexpected internal server error occurred.' ,
66- // Avoid leaking sensitive details in production responses
67- // 'details': e.toString(), // Maybe include in dev mode only
68- },
69- },
53+ exception: const UnknownException ('An unexpected internal server error occurred.' ),
54+ context: context,
7055 );
7156 }
7257 };
@@ -108,3 +93,51 @@ String _mapExceptionToCodeString(HtHttpException exception) {
10893 _ => 'unknownError' , // Default
10994 };
11095}
96+
97+ /// Creates a standardized JSON error response with appropriate CORS headers.
98+ ///
99+ /// This helper ensures that error responses sent to the client include the
100+ /// necessary `Access-Control-Allow-Origin` header, allowing the client-side
101+ /// application to read the error message body.
102+ Response _jsonErrorResponse ({
103+ required int statusCode,
104+ required HtHttpException exception,
105+ required RequestContext context,
106+ }) {
107+ final errorCode = _mapExceptionToCodeString (exception);
108+ final headers = < String , String > {
109+ HttpHeaders .contentTypeHeader: 'application/json' ,
110+ };
111+
112+ // Add CORS headers to error responses. This logic is environment-aware.
113+ // In production, it uses a specific origin from `CORS_ALLOWED_ORIGIN`.
114+ // In development (if the variable is not set), it allows any localhost.
115+ final requestOrigin = context.request.headers['Origin' ];
116+ if (requestOrigin != null ) {
117+ final allowedOrigin = EnvironmentConfig .corsAllowedOrigin;
118+
119+ var isOriginAllowed = false ;
120+ if (allowedOrigin != null ) {
121+ // Production: Check against the specific allowed origin.
122+ isOriginAllowed = (requestOrigin == allowedOrigin);
123+ } else {
124+ // Development: Allow any localhost origin.
125+ isOriginAllowed = (Uri .tryParse (requestOrigin)? .host == 'localhost' );
126+ }
127+
128+ if (isOriginAllowed) {
129+ headers[HttpHeaders .accessControlAllowOriginHeader] = requestOrigin;
130+ headers[HttpHeaders .accessControlAllowCredentialsHeader] = 'true' ;
131+ headers[HttpHeaders .accessControlAllowMethodsHeader] =
132+ 'GET, POST, PUT, DELETE, OPTIONS' ;
133+ headers[HttpHeaders .accessControlAllowHeadersHeader] =
134+ 'Origin, Content-Type, Authorization' ;
135+ }
136+ }
137+
138+ return Response .json (
139+ statusCode: statusCode,
140+ body: {'error' : {'code' : errorCode, 'message' : exception.message}},
141+ headers: headers,
142+ );
143+ }
0 commit comments