feat(dio): propagate custom exception types from interceptors (#1950)#2514
feat(dio): propagate custom exception types from interceptors (#1950)#2514realmeylisdev wants to merge 4 commits into
Conversation
) Add `DioException.custom(...)` factory and `rejectCustomError(...)` helpers on the three interceptor handlers, allowing developers to propagate the original exception type from inside an `Interceptor` to the caller of the request method. The marker survives traversal through subsequent `onError` interceptors, through `QueuedInterceptor`, and through `copyWith`. Unwrap happens at the single funnel in `DioMixin.fetch()` via `Error.throwWithStackTrace`. Default behavior is unchanged: existing `DioException` constructors and factories leave `propagateInnerError: false`, so callers that `on DioException catch (e)` continue to work without modification. Continuation of prior work in cfug#2484 (closed because the contributor's fork was deleted, not for design reasons). API shape follows @kuhnroyal's suggestion in cfug#1950 (comment). Original issue filed by @WutangNailao. Resolves cfug#1950.
|
Thanks! Wondered what happened to the last PR. I only have a phone for the next 10 days, will try to review afterwards. |
|
Old fork got deleted on my end, which auto-closed it — no design issues, just sat with only a Copilot review. This PR is the same |
kuhnroyal
left a comment
There was a problem hiding this comment.
Looking good, just 2 ideas for some consistency in naming but I would be fine either way.
| /// See [DioException.custom] for details. | ||
| /// | ||
| /// Resolves https://github.com/cfug/dio/issues/1950. | ||
| void rejectCustomError( |
There was a problem hiding this comment.
I would probably go with just rejectCustom everywhere
| /// behavior is unchanged. | ||
| /// | ||
| /// Resolves https://github.com/cfug/dio/issues/1950. | ||
| final bool propagateInnerError; |
|
(To resolve the current CI failure, update the branch to the latest for recent fixes) |
Signed-off-by: Meylis <meylis@divine.video>
After merging main, the cfug#2499 async-interceptor error zone routes uncaught async throws through assureDioException, whose DioException short-circuit preserves the propagateInnerError marker. Lock in that an async `throw DioException.custom(...)` (which hung before the merge) still unwraps to the inner type at the funnel. Signed-off-by: Meylis <meylis@divine.video>
|
Thanks @AlexV525 — merged latest The merge is disjoint from the #1950 custom-exception funnel — Locally green: The new workflow runs are showing |
Code Coverage Report: Only Changed Files listed
Minimum allowed coverage is |
Summary
Adds an opt-in mechanism for developers to propagate custom exception types from interceptors back to the caller of
dio.get/post/..., so thattry { … } on MyException catch (e)matches at the await boundary instead of always seeingDioException.Resolves #1950.
Continuation of prior work in #2484 (closed because the contributor's fork was deleted, not for design reasons). API shape follows @kuhnroyal's suggestion in #1950 (comment). Original issue filed by @WutangNailao.
Reproducing the original problem (without this PR)
Before this PR:
on MyApiExceptionnever matches. The exception is always wrapped asDioException; the original is accessible only viae.error.After this PR (opt-in):
The equivalent throw-based form also works:
Changes
DioException.custom(...)factory and instance flagpropagateInnerError(defaultfalse).rejectCustomError(...)helper onRequestInterceptorHandler,ResponseInterceptorHandler, andErrorInterceptorHandler.DioMixin.fetch()'s final catch usingError.throwWithStackTraceto preserve the throw-site stack.copyWithcarries the flag (so downstreamonErrorinterceptors can enrich without losing the marker).dio/README.md: new "Throwing custom exceptions from interceptors" subsection +propagateInnerErrorfield listing.dio/CHANGELOG.md: Unreleased entry.Backwards compatibility
Default behavior unchanged. Every existing
on DioException catch (e)keeps working — the new behavior is strictly opt-in via the new factory or helper. A regression sentinel test (unmarked DioException(error: customEx) still wraps) verifies this.Compatibility Policy unaffected (no SDK bump).
Error.throwWithStackTraceis in Dart 2.16; dio's floor is 2.18.Why this design
DioMixin.fetch()'s finalcatch; unwrap there is sufficient and safe (transport errors created by_dispatchRequestnever setpropagateInnerError).assureDioException's short-circuit (if (error is DioException) return error;). No need to teacherrorInterceptorWrapper,QueuedInterceptor._handleRequest/Response/Error, or_dispatchRequestabout the marker — they already pass through DioException unchanged.Test plan
Added 9 tests in
dio/test/interceptor_test.dartunder groupCustom exception unwrapping (#1950):handler.rejectCustomErrorfromonResponsepropagates inner typehandler.rejectCustomErrorfromonRequestpropagates inner typehandler.rejectCustomErrorfromonErrorpropagates inner typethrow DioException.custom(...)directly from interceptor propagates inner typeDioException(error: customEx)still wraps asDioExceptioncopyWithpreservespropagateInnerErrorthrough anonErrorchain (withcallFollowingErrorInterceptor: true)rejectCustomErrorworks insideQueuedInterceptorDioException.customfactory setspropagateInnerError: trueDioExceptiondefault constructor leavespropagateInnerError: falseVerified locally:
melos run format(CI-strict): cleandart analyze --fatal-infosondiopackage: no issuesdioVM test suite: all 38 interceptor tests + all 4 queued-interceptor tests pass; broader suite green except fortest/test_suite_test.dart: download delete on cancelwhich is a pre-existing flake against externalhttpbun.com(passes on isolated re-run; unrelated to this change).Files changed
dio/lib/src/dio_exception.dart— flag, factory, copyWithdio/lib/src/interceptor.dart— threerejectCustomErrorhelpersdio/lib/src/dio_mixin.dart— funnel unwrapdio/test/interceptor_test.dart— 9 new tests + helper classdio/CHANGELOG.md— Unreleased entrydio/README.md— docs subsection +propagateInnerErrorfield