Skip to content

feat: add custom exception unwrapping in interceptors#2484

Open
meylis1998 wants to merge 1 commit intocfug:mainfrom
meylis1998:feature/custom-exception-unwrapping
Open

feat: add custom exception unwrapping in interceptors#2484
meylis1998 wants to merge 1 commit intocfug:mainfrom
meylis1998:feature/custom-exception-unwrapping

Conversation

@meylis1998
Copy link

Summary

Changes

  • Add throwInnerErrorOnFinish flag to DioException (default false)
  • Add DioException.customError() factory constructor for convenient creation
  • Add rejectCustomError() method to RequestInterceptorHandler, ResponseInterceptorHandler, and ErrorInterceptorHandler
  • Unwrap marked exceptions in fetch() final catch block

Example Usage

// In interceptor
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!
}

Test plan

  • Added tests for rejectCustomError() from all three handler types
  • Added test verifying regular reject() still wraps in DioException
  • Added tests for DioException.customError() factory
  • Added tests for copyWith() preserving the flag
  • Added test for callFollowingErrorInterceptor parameter
  • Added test for QueuedInterceptor support
  • All existing tests pass

Closes #1950

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
@meylis1998 meylis1998 requested a review from a team as a code owner January 30, 2026 15:27
@AlexV525 AlexV525 requested a review from Copilot March 5, 2026 07:51
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 throwInnerErrorOnFinish on DioException plus a DioException.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.

Comment on lines +526 to +528
// instead of the DioException wrapper.
if (dioException.throwInnerErrorOnFinish && dioException.error != null) {
throw dioException.error!;
Copy link

Copilot AI Mar 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)).

Suggested change
// 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,
);

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Developer should throw custom exception in interceptor

2 participants