-
Notifications
You must be signed in to change notification settings - Fork 306
(wip/draft) refactor: convert from snafu to n0-error #3561
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?
Conversation
|
Documentation for this PR has been generated and is available at: https://n0-computer.github.io/iroh/pr/3561/docs/iroh/ Last updated: 2025-10-21T07:49:51Z |
32458a9 to
7f59c66
Compare
| Ok(len) => { | ||
| if len != PublicKey::LENGTH { | ||
| return Err(DecodeInvalidLengthSnafu.build()); | ||
| return Err!(KeyParsingError::DecodeInvalidLength); |
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.
looking at this, I would much rather write
bail!(KeyParsingError::DecodeInvalidLength);overloading Err I find problematic personally
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.
Yes, I can add such a bail! macro. Without bail! or theErr! macro it would be
Err(e!(KeyParsingError::DecodeInvalidLength))
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 that would be better to have than Err!
| self.as_verifying_key() | ||
| .verify_strict(message, &signature.0) | ||
| .map_err(|_| SignatureSnafu.build()) | ||
| .map_err(|_| e!(SignatureError)) |
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.
does this have to be a macro, or could this be a method on SignatureError?
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.
Right, this is a struct so it can just be SignatureError::new() in this case. However, for enums I removed the constructors from the proc macro generated code (I had them before) so that jump-to-definition on variants works.
| let expected = Self::KIND; | ||
| let Some(rest) = str.strip_prefix(expected) else { | ||
| return Err(KindSnafu { expected }.build()); | ||
| return Err(ParseError::wrong_prefix(expected)); |
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.
shouldn't this be called ParseError::kind(expected)?
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.
nvm, found the impl below
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.
could we have a macro so we can write
bail!(ParseError::Kind { expected });without the indirection method?
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.
Yeah, this was just kept from existing code. It would either be bail or return Err!(ParseError::Kind { expected }) or return Err(e!(ParseError::Kind { expected })). Which one we want is to be bikeshedded.
| } else { | ||
| let path = dirs_next::data_dir() | ||
| .context("operating environment provides no directory for application data")?; | ||
| .std_context("operating environment provides no directory for application data")?; |
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 this works reasonably well, a bit confusing at first, but easy to use at the end of the day
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.
To me, this is the most annoying thing about this whole endevour, but unfortunately I don't think there's another solution. I made the std_context method the "longer" one (to type) to nudge towards using context wherever possible (ie. on our errors that implement StackError) because std_context will work on our errors too, but we'll lose the location info in the process. This is the much discussed issue that as long as we implement std::error::Error for our errors, we can't forbid calling the StdResultExt methods on them, even though we should always use the StackResultExt methods (which are only available on StackError results, not on std error results).
| Ok(Err(err)) => { | ||
| warn!(?err, "task failed"); | ||
| final_res = Err(err).context("task"); | ||
| final_res = Err(err).std_context("task"); |
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.
could we have something like
final_res = whatever_std!(err, "task");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 can. Here it even can be the same macro for both StackError and std error, because we can use autoref specialization.
There is already a anyerr! macro that makes use of autoref specialization to not have to worry about whether the error is std or StackError. With this it would be Err(anyerr!(err).context("task")). I can expand the macro to support context as second arg, so it could be Err(anyerr!(err, "task")). Will do that.
Not sure if we need a variant that returns Err(err), I think it's fine to write that by hand (like anyhow::anyhow!, it also returns the error not a result).
| url: dial_url.clone(), | ||
| } | ||
| .build() | ||
| e!(ConnectError::InvalidWebsocketUrl { |
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.
this macro fills in the location details?
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.
Yes. It expands to ConnectError::InvalidWebsocketUrl { url: dial_url.clone(), meta: Default::default() }
| snafu::ensure!(size <= MAX_PACKET_SIZE, ExceedsMaxPacketSizeSnafu { size }); | ||
| n0_error::ensure!( | ||
| size <= MAX_PACKET_SIZE, | ||
| n0_error::e!(SendError::ExceedsMaxPacketSize { size }) |
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.
could we make the macro such that we don't need to call e! inside?
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 can but then it wouldn't be usable with other error types anymore as it would add a meta field and if that doesn't exist it would give a compile error. We could add another macro ensure_e! or such, but not sure if that is really better than the current one. Will change to import e though, then it's much shorter already.
| }) | ||
| .await??; | ||
| .await | ||
| .map_err(|err| e!(DialError::Timeout, err))??; |
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.
what does this variant do?
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.
Expand to DialError::Timeout { source: err, meta: Meta::default() }
See docs: https://n0-computer.github.io/n0-error/pr/4/docs/n0_error/macro.e.html
| let upgraded = hyper::upgrade::on(res).await.context(ProxyConnectSnafu)?; | ||
| let upgraded = hyper::upgrade::on(res) | ||
| .await | ||
| .map_err(|err| e!(DialError::ProxyConnect, err))?; |
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.
can't we do this with one of the context variants?
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.
context takes a string (or, impl Display) and adds this as context. We can't do without the e! macro if we want jump-to-usage on ProxyConnect to work and not type meta: Default::default() manually. So not sure what context should do here?
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.
yeah, the filling in the struct wouldn't be possible :(
| errors: Vec<LookupError>, | ||
| } | ||
|
|
||
| // no manual new; use e!(Type { errors }) |
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.
random 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.
will remove
|
Some examples when tests are failing: Here we get the same message twice, because the outer error delegates to an inner error with Same: the outer error delegates to the next with Another one: |
Description
Note: 90% of this PR are written by an AI (OpenAI codex) which I instructed in numerous prompts to convert iroh from n0-snafu/snafu to n0-error. I told it to keep context in
error-reporting.agents.md, which I added to the PR (will be removed before merge) for easy alteration.*Note: The following description is also AI generated.
Migrate
snafu/n0-snafu→n0_erroracross iroh workspaceRationale
n0_error(meta, display, source chaining). Remove per-cratesnafu/n0-snafu. Consistent ergonomics across libs, bins, and examples.Highlights
snafu/n0-snafuwithn0_errorin:iroh,iroh-relay,iroh-base,iroh-dns-server, benches, examples, docs..std_context(..)for std/3rd-party errors;.context(..)only for localStackErrortypes; use.e()in examples/tests where needed forAnyError.snafu::whatever!→n0_error::whatever!;ensure_whatever!→ explicitif !cond { whatever!(..) }.add_meta/derive(Error): prefer#[error(from_sources, std_sources)]when feasible.Gotchas
JoinError,WriteError,ReadToEndError,ClosedStream) don’t implementStackErrorin examples; use.e()?or.std_context(..).?for localStackErrorresults; remove unnecessary.e()on internalResult<_>; re-add.e()only where compilation requiresAnyError.Review focus
.context(..)vs.std_context(..).Breaking Changes
Notes & open questions
Change checklist
quic-rpciroh-gossipiroh-blobsdumbpipesendme