feat: add custom exception unwrapping in interceptors#2484
feat: add custom exception unwrapping in interceptors#2484meylis1998 wants to merge 1 commit intocfug:mainfrom
Conversation
Add mechanism to throw custom exceptions directly from interceptors
instead of wrapping them in DioException.
- Add `throwInnerErrorOnFinish` flag to DioException
- Add `DioException.customError()` factory constructor
- Add `rejectCustomError()` method to all interceptor handlers
- Unwrap marked exceptions in fetch() final catch block
This allows callers to catch custom exception types directly:
```dart
dio.interceptors.add(InterceptorsWrapper(
onResponse: (response, handler) {
if (response.statusCode == 401) {
handler.rejectCustomError(
UnauthorizedException('Token expired'),
response.requestOptions,
);
return;
}
handler.next(response);
},
));
// Caller can now catch custom exception directly:
try {
await dio.get('/api');
} on UnauthorizedException catch (e) {
// Now works!
}
```
Closes cfug#1950
There was a problem hiding this comment.
Pull request overview
This PR adds first-class support for propagating custom exceptions thrown from interceptors to callers (instead of always wrapping them in DioException), addressing issue #1950.
Changes:
- Introduces
throwInnerErrorOnFinishonDioExceptionplus aDioException.customError()factory to mark exceptions intended for unwrapping. - Adds
rejectCustomError()helper APIs to interceptor handlers to create/propagate marked errors consistently. - Updates
DioMixin.fetch()to unwrap and throw the inner error at the final normalization catch; adds interceptor-level tests validating behavior.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
dio/lib/src/dio_exception.dart |
Adds unwrapping flag + customError() factory and preserves flag via copyWith(). |
dio/lib/src/interceptor.dart |
Adds rejectCustomError() to request/response/error interceptor handlers. |
dio/lib/src/dio_mixin.dart |
Unwraps marked DioExceptions in the final fetch() catch block. |
dio/test/interceptor_test.dart |
Adds tests for unwrapping behavior, factory, copyWith(), callFollowingErrorInterceptor, and queued interceptors. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // instead of the DioException wrapper. | ||
| if (dioException.throwInnerErrorOnFinish && dioException.error != null) { | ||
| throw dioException.error!; |
There was a problem hiding this comment.
Unwrapping via throw dioException.error!; replaces the original stack trace with the stack trace from this catch block. That makes custom exceptions harder to debug since dioException.stackTrace (captured earlier) is lost. Consider throwing the inner error with the original stack trace (e.g., Error.throwWithStackTrace(dioException.error!, dioException.stackTrace)).
| // instead of the DioException wrapper. | |
| if (dioException.throwInnerErrorOnFinish && dioException.error != null) { | |
| throw dioException.error!; | |
| // instead of the DioException wrapper, preserving its original | |
| // stack trace when available. | |
| if (dioException.throwInnerErrorOnFinish && dioException.error != null) { | |
| Error.throwWithStackTrace( | |
| dioException.error!, | |
| dioException.stackTrace ?? StackTrace.current, | |
| ); |
Summary
DioExceptionChanges
throwInnerErrorOnFinishflag toDioException(defaultfalse)DioException.customError()factory constructor for convenient creationrejectCustomError()method toRequestInterceptorHandler,ResponseInterceptorHandler, andErrorInterceptorHandlerfetch()final catch blockExample Usage
Test plan
rejectCustomError()from all three handler typesreject()still wraps inDioExceptionDioException.customError()factorycopyWith()preserving the flagcallFollowingErrorInterceptorparameterQueuedInterceptorsupportCloses #1950