4
4
import 'dart:io' ;
5
5
6
6
import 'package:dart_frog/dart_frog.dart' ;
7
+ import 'package:ht_api/src/config/environment_config.dart' ;
7
8
import 'package:ht_shared/ht_shared.dart' ;
8
9
import 'package:json_annotation/json_annotation.dart' ;
9
10
@@ -19,54 +20,38 @@ Middleware errorHandler() {
19
20
} on HtHttpException catch (e, stackTrace) {
20
21
// Handle specific HtHttpExceptions from the client/repository layers
21
22
final statusCode = _mapExceptionToStatusCode (e);
22
- final errorCode = _mapExceptionToCodeString (e);
23
23
print ('HtHttpException Caught: $e \n $stackTrace ' ); // Log for debugging
24
- return Response . json (
24
+ return _jsonErrorResponse (
25
25
statusCode: statusCode,
26
- body: {
27
- 'error' : {'code' : errorCode, 'message' : e.message},
28
- },
26
+ exception: e,
27
+ context: context,
29
28
);
30
29
} on CheckedFromJsonException catch (e, stackTrace) {
31
30
// Handle json_serializable validation errors. These are client errors.
32
31
final field = e.key ?? 'unknown' ;
33
32
final message = 'Invalid request body: Field "$field " has an '
34
33
'invalid value or is missing. ${e .message }' ;
35
34
print ('CheckedFromJsonException Caught: $e \n $stackTrace ' );
36
- return Response . json (
35
+ return _jsonErrorResponse (
37
36
statusCode: HttpStatus .badRequest, // 400
38
- body: {
39
- 'error' : {
40
- 'code' : 'invalidField' ,
41
- 'message' : message,
42
- },
43
- },
37
+ exception: InvalidInputException (message),
38
+ context: context,
44
39
);
45
40
} on FormatException catch (e, stackTrace) {
46
41
// Handle data format/parsing errors (often indicates bad client input)
47
42
print ('FormatException Caught: $e \n $stackTrace ' ); // Log for debugging
48
- return Response . json (
43
+ return _jsonErrorResponse (
49
44
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,
56
47
);
57
48
} catch (e, stackTrace) {
58
49
// Handle any other unexpected errors
59
50
print ('Unhandled Exception Caught: $e \n $stackTrace ' );
60
- return Response . json (
51
+ return _jsonErrorResponse (
61
52
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,
70
55
);
71
56
}
72
57
};
@@ -108,3 +93,51 @@ String _mapExceptionToCodeString(HtHttpException exception) {
108
93
_ => 'unknownError' , // Default
109
94
};
110
95
}
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