-
Notifications
You must be signed in to change notification settings - Fork 324
refactor: introduce structured error system w/ host exception tunneling
#1380
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
refactor: introduce structured error system w/ host exception tunneling
#1380
Conversation
|
@Haleshot Really fast! Looks very reasonable! Thanks! |
Signed-off-by: Srihari Thyagarajan <hari.leo03@gmail.com>
georgeh0
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The API looks good! Left a few comments for implementations. Feel free to start migrating existing code to the new error type.
…_bail` for _consistent_ error handling throuhgout
|
Updated the PR description above with "Edits" to reflect the changes made incrementally; awaiting your review :) |
| #[derive(Debug)] | ||
| pub struct PyErrWrapper(PyErr); | ||
|
|
||
| impl PyErrWrapper { | ||
| pub fn new(err: PyErr) -> Self { | ||
| Self(err) | ||
| } | ||
|
|
||
| pub fn into_inner(self) -> PyErr { | ||
| self.0 | ||
| } | ||
| } | ||
|
|
||
| impl Display for PyErrWrapper { | ||
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||
| write!(f, "{}", self.0) | ||
| } | ||
| } | ||
|
|
||
| impl std::error::Error for PyErrWrapper {} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
seems this PyErrWrapper doesn't provide additional values. We may not need this layer.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see it asa thin wrapper, but PyErr doesn't impl std::error::Error which HostLang(Box<dyn HostError>) needs (some bridging is needed in this case). Were you thinking of storing PyErr directly in a dedicated variant (CError::PyErr(PyErr) variant instead of using trait object?), or relaxing the trait bounds? Happy to go either direction.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We still want to keep the generic HostLang(Box<dyn HostError>) for future proof.
Looks like PyErr actually implemented std::error::Error?
https://docs.rs/pyo3/latest/pyo3/struct.PyErr.html#impl-Error-for-PyErr
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ahh, yes 🙈- PyErr does impl Error, thanks for the relevant docs webpage. Will remove PyErrWrapper and use PyErr directly with CError::host(err).
| } | ||
|
|
||
| match err.without_contexts() { | ||
| CError::Client { msg, .. } => PyValueError::new_err(msg.clone()), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think for client error, context is also valuable. Probably the only difference is backtrace is not necessary.
| pub fn is_host_error(&self) -> bool { | ||
| self.find_host_error().is_some() | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually both is_host_error() and is_client_error() are redundant (they're not used by any code any way, other than code to test themselves). Using without_contexts() can easily achieve the same goal. We don't need to create a tester for each error type.
Similar to find_host_error(). Callers can use without_contexts() + matcher directly too (and they need a mater any way for the Option).
|
About
There're two parts:
For 1, I think it's it's OK to handle different parts separately. The final goal is not to use For 2, the problem of using To make the migration easier, I have another idea. A lot of existing crates just use
In this way, most modules won't need to be touched, if they're just propagating errors rather than creating errors. What do you think? |
|
On the approach suggested above: I'll go about doing the following:
Also, hope it's fine for putting this (& you) through multiple review rounds 😅 - definitely not intentional (hope you don't mind)! You're also more than welcome to push directly to this branch if you feel I'm not getting it (still making my way about Rust and the codebase. |
Looks good! No worries, this is a large refactor and it's well expected to have multiple rounds of review :) Thanks a lot for pushing it through! |
Addresses #1377 by introducing a structured error system that supports host exception tunneling. The core
CErrorenum has four variants:Contextfor wrapping errors with messages (replacinganyhow::Context),HostLangfor tunneling host exceptions viaBox<dyn HostError>,Clientfor validation/4xx errors, andInternalfor 5xx errors; both of which I believe, capture the backtraces via/std::backtrace::Backtrace.On the Python side,
PyErrWrapperimplementsHostErrorandcerror_to_pyerr()handles the reverse conversion, traversing the context chain and downcasting viaAnyto extract the originalPyErrif present.Following your suggestion about incremental/iteratively working within the same PR, I'm pushing this first for your review. If this looks good, the next step is migrating the a lot of
anyhow::Resultusages across the codebase to useCResult. The existinganyhow-based types should/will be preserved for backwards compatibility during that migration.Edit 1: Tended the changes requested here and here; can proceed with the migration of
anyhow::Resultusages if it looks good!Edit 2: Completed the migration of error macros from
anyhow::bail!/anyhow::anyhow!to the new structuredclient_bail!/client_error!andinternal_bail!/internal_error!macros across ~35 files.The new macros create
CErrorwithClient/Internalvariants that capture backtraces, then convert toanyhow::Errorfor compatibility with existing return types. This preserves all the structured error semantics (including host exception tunneling for Python) while avoiding a massive signature refactor. Theanyhow::Resulttype remains as-is...PS: I took, what I believe is a pragmatic approach here - rather than replacing every
anyhow::Result<T>withCResult<T>(which would've required touching hundreds of serde impls and function signatures), I wrappedCErrorinsideanyhow::Error. Is this acceptable, or would you prefer a fullCResult<T>migration in a follow-up?Full disclosure: Claude (via Cursor) helped me grep through the (relevant files in the) codebase to identify all the error macro usages. Turns out clicking through search results in an IDE beats wrestling w/ git grepping 🙃