Skip to content
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

Use proper error types #3192

Open
casey opened this issue Feb 28, 2024 · 10 comments
Open

Use proper error types #3192

casey opened this issue Feb 28, 2024 · 10 comments

Comments

@casey
Copy link
Collaborator

casey commented Feb 28, 2024

Currently, we use anyhow for all errors. I like anyhow, but it makes it too easy to bubble up an error without context, which leads to a less-than-useful error message. For example, I/O errors don't include the offending file, making them quite inscrutable. We should probably switch to using something like snafu or thiserror.

@cryptoni9n
Copy link
Collaborator

Hi @casey & @raphjaph - I've started working on converting the code base over to thiserror and wanted to check-in with you to make sure that this is still desired and won't all be for nought.

I've created an errors.rs under /src and am stuffing most of the custom errors into it.

I started out converting the files underneath /src/subcommands/wallet and it just kind of snowballed from there. I've been working on the src/settings.rs and that one is requiring many, many changes.

I know that this is a ton of changes, but I hope to get through it all eventually. I'm not sure exactly how I should submit the changes once I can get everything ready.

Any thoughts/feedback?

@cryptoni9n cryptoni9n self-assigned this Jun 25, 2024
@casey
Copy link
Collaborator Author

casey commented Jun 25, 2024

Thanks for tackling this! I think it will make error messages much more useful, and I wish we did it from the beginning.

I actually think that prefer snafu over thiserror. The context selector is a very nice way to select an error type. For example:

#[derive(Snafu)]
enum Error {
  #[snafu(display("I/O error at `{}`", path.display()))]
  Io { path: PathBuf, source: io::Error}
}

Then, from another module:

do_something(&path).context(error::Io { path: &path })?;

Is there any way to do this piecemeal? It will be an absolutely massive PR, and will conflict with basically every other PR.

Like converting the top-level error type to:

#[derive(Snafu)]
enum Error {
  #[snafu(display("{0}"))]
  Any(Box<dyn std::Error::Error),
  #[snafu(display("I/O error at `{}`", path.display()))]
  Io { path: PathBuf, source: io::Error}
}

And then adding error cases one by one.

@cryptoni9n
Copy link
Collaborator

cryptoni9n commented Jun 25, 2024

Thanks for the quick response, Casey. If I understand you correctly (and I may not), the context selector that you've mentioned seems very similar to what I've been doing with thiserror. The errors.rs creates the MyError enum:

use std::env::VarError;
use std::io;
use std::num::ParseIntError;
use std::num::TryFromIntError;
use thiserror::Error;
use redb::TransactionError;

#[derive(Error, Debug)]

pub enum MyError {
  #[error("Invalid environment variable `{var}`: `{value}`")]
  InvalidEnvVar { var: String, value: String },
  },

and the errors are called as shown in this arguments.rs:

use {
  super::*,
  clap::builder::styling::{AnsiColor, Effects, Styles},
};

#[derive(Debug, Parser)]
#[command(
  version,
  styles = Styles::styled()
    .header(AnsiColor::Green.on_default() | Effects::BOLD)
    .usage(AnsiColor::Green.on_default() | Effects::BOLD)
    .literal(AnsiColor::Blue.on_default() | Effects::BOLD)
    .placeholder(AnsiColor::Cyan.on_default()))
]
pub(crate) struct Arguments {
  #[command(flatten)]
  pub(crate) options: Options,
  #[command(subcommand)]
  pub(crate) subcommand: Subcommand,
}

impl Arguments {
  pub(crate) fn run(self) -> Result<Option<Box<dyn subcommand::Output>>, MyError> {
    let mut env: BTreeMap<String, String> = BTreeMap::new();

    for (var, value) in env::vars_os() {
      let Some(var) = var.to_str() else {
        continue;
      };

      let Some(key) = var.strip_prefix("ORD_") else {
        continue;
      };

      env.insert(
        key.into(),
        value
          .into_string()
          .map_err(|value| MyError::InvalidEnvVar {
            var: var.to_string(),
            value: value.to_string_lossy().to_string(),
          })?,
      );
    }

    self.subcommand.run(Settings::load(self.options)?)
  }
}

I'm not certain if it can be done piecemeal or not yet, but I have a hunch that it can be. I was thinking I could leave anyhow in and just convert the files currently throwing compile errors on my build. I imagine that I could get it to start compiling without fully converting to 'thiserror' and could push those changes as piece A (with blessing) before finishing off the back half (or however much) and then permanently removing anyhow after it's fully replaced. I could be wrong though and it could make more sense to bang it all out in one go. (edit: or rather still, it may be required to be done all at once)

I've done a good amount of conversion already, but I really have no idea if it is 1% or 10% - only been working on it for a couple of days. Happy to switch horses though - just say the word.

@casey
Copy link
Collaborator Author

casey commented Jun 25, 2024

Check out #3832, which can be merged as a start and more errors can be done in a piecemeal fashion. I think doing it piecemeal is the way to go. The diff would be absolutely huge otherwise and hard to review, and the merge conflicts would be brutal. I think doing it file or module at a time are the way to go.

@casey casey closed this as completed Jun 25, 2024
@casey
Copy link
Collaborator Author

casey commented Jun 25, 2024

Whoops, didn't mean to close.

@casey casey reopened this Jun 25, 2024
@casey
Copy link
Collaborator Author

casey commented Jun 27, 2024

@cryptoni9n I merged #3832, which uses snafu to make it easier to use typed errors. Check out that PR, which adds a couple new error variants. It introduces a SnafuError and .snafu_context and .with_snafu_context methods, which can be removed once the conversion is complete, and SnafuError renamed to just Error.

@cryptoni9n
Copy link
Collaborator

@cryptoni9n I merged #3832, which uses snafu to make it easier to use typed errors. Check out that PR, which adds a couple new error variants. It introduces a SnafuError and .snafu_context and .with_snafu_context methods, which can be removed once the conversion is complete, and SnafuError renamed to just Error.

Will do - thanks for getting this started!

@casey
Copy link
Collaborator Author

casey commented Jun 27, 2024

You bet! Feel free to ask for help, snafu, and in particular the context selectors, are tricky.

@cryptoni9n
Copy link
Collaborator

You bet! Feel free to ask for help, snafu, and in particular the context selectors, are tricky.

Yeah, I was banging my head against it for a little bit earlier today. I'm just starting my vacation now though, so I won't really be able to dive in until I get back in about 10 days.

@casey
Copy link
Collaborator Author

casey commented Jun 27, 2024

No worries, have fun on vacation!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants