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

Replace thiserror with SNAFU #379

Draft
wants to merge 6 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 26 additions & 5 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ serde_derive = "1.0.204"
serde_json = "1.0.120"
serde_yaml = "0.9.34"
sha2 = "0.10.8"
snafu = "0.8.5"
strum = { version = "0.26.3", features = ["derive"] }
thiserror = "2.0.3"
thread-priority = "1.1.0"
Expand Down
4 changes: 2 additions & 2 deletions boulder/src/container.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@ use crate::Paths;

pub fn exec<E>(paths: &Paths, networking: bool, f: impl FnMut() -> Result<(), E>) -> Result<(), Error>
where
E: std::error::Error + 'static,
E: std::error::Error + Send + Sync + 'static,
{
run(paths, networking, f)
}

fn run<E>(paths: &Paths, networking: bool, f: impl FnMut() -> Result<(), E>) -> Result<(), Error>
where
E: std::error::Error + 'static,
E: std::error::Error + Send + Sync + 'static,
{
let rootfs = paths.rootfs().host;
let artefacts = paths.artefacts();
Expand Down
2 changes: 1 addition & 1 deletion boulder/src/draft/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ mod cmake;
mod meson;
mod python;

pub type Error = Box<dyn std::error::Error>;
pub type Error = Box<dyn std::error::Error + Send + Sync>;

/// A build system
#[derive(Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd, strum::Display)]
Expand Down
2 changes: 1 addition & 1 deletion boulder/src/package/analysis.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ use super::collect::{Collector, PathInfo};

mod handler;

pub type BoxError = Box<dyn std::error::Error>;
pub type BoxError = Box<dyn std::error::Error + Send + Sync>;

pub struct Chain<'a> {
handlers: Vec<Box<dyn Handler>>,
Expand Down
2 changes: 1 addition & 1 deletion crates/config/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,4 @@ dirs.workspace = true
fs-err.workspace = true
serde.workspace = true
serde_yaml.workspace = true
thiserror.workspace = true
snafu.workspace = true
26 changes: 13 additions & 13 deletions crates/config/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use std::{

use fs_err as fs;
use serde::{de::DeserializeOwned, Serialize};
use thiserror::Error;
use snafu::{ResultExt as _, Snafu};

const EXTENSION: &str = "yaml";

Expand Down Expand Up @@ -74,13 +74,13 @@ impl Manager {

let dir = self.scope.save_dir(&domain);

fs::create_dir_all(&dir).map_err(|io| SaveError::CreateDir(dir.clone(), io))?;
fs::create_dir_all(&dir).context(CreateDirSnafu)?;

let path = dir.join(format!("{name}.{EXTENSION}"));

let serialized = serde_yaml::to_string(config)?;
let serialized = serde_yaml::to_string(config).context(SerializeSnafu)?;

fs::write(&path, serialized).map_err(|io| SaveError::Write(path, io))?;
fs::write(&path, serialized).context(WriteSnafu)?;
Copy link
Collaborator

Choose a reason for hiding this comment

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

What's the benefit of sanfu over thiserror? It seems like we're just exchanging map_err for a custom trait / .context but it provides the exact same utility in the end?

All the extra variants being added in this PR is great, but can just as easily be done with thiserror

Copy link
Member Author

Choose a reason for hiding this comment

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

Two benefits:

  • The context stuff is shorter, especially when adding extra context. This makes it practical to not implement From<UnderlyingError> for MyError and in fact snafu's derive macro never implements this.
  • As a consequence, it becomes more apparent where errors are being converted, so it is much easier to notice / review whether a particular error variant is overused – I've seen some Io and Nix error variants, many of which would likely benefit from being split up.

Copy link
Collaborator

Choose a reason for hiding this comment

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

We can omit #[from] when defining variants with thiserror, but adding more friction here is definitely a good thing. The catch all Io, etc patterns are no good. If we can mostly solve that on the library portions of this repo then that'd be a big win, regardless of which error lib we use (I ultimately have no preference, they're effectively the same)

Copy link
Collaborator

Choose a reason for hiding this comment

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

I'm thankful you're looking at this, thank you! I'll jump back on matrix next week and it'd be good to connect on errors. It'd be great to focus on user facing errors next (moss / boulder CLI stuff) with anyhow / eyre. We're using thiserror there too and it's definitely the wrong approach.

Copy link
Collaborator

Choose a reason for hiding this comment

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

As an example I have this old draft:

#170


Ok(())
}
Expand All @@ -97,18 +97,18 @@ impl Manager {
}
}

#[derive(Debug, Error)]
#[error("$HOME or $XDG_CONFIG_HOME env not set")]
#[derive(Debug, Snafu)]
#[snafu(display("$HOME or $XDG_CONFIG_HOME env not set"))]
pub struct CreateUserError;

#[derive(Debug, Error)]
#[derive(Debug, Snafu)]
pub enum SaveError {
#[error("create config dir {0:?}")]
CreateDir(PathBuf, #[source] io::Error),
#[error("serialize config")]
Yaml(#[from] serde_yaml::Error),
#[error("write config file {0:?}")]
Write(PathBuf, #[source] io::Error),
#[snafu(display("create config dir"))]
CreateDir { source: io::Error },
#[snafu(display("serialize config"))]
Serialize { source: serde_yaml::Error },
#[snafu(display("write config file"))]
Write { source: io::Error },
}

fn enumerate_paths(entry: Entry, resolve: Resolve<'_>, domain: &str) -> Vec<PathBuf> {
Expand Down
2 changes: 1 addition & 1 deletion crates/container/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,5 @@ rust-version.workspace = true
[dependencies]
fs-err.workspace = true
nix.workspace = true
snafu.workspace = true
strum.workspace = true
thiserror.workspace = true
40 changes: 23 additions & 17 deletions crates/container/src/idmap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,15 @@ use std::process::Command;

use fs_err as fs;
use nix::unistd::{getgid, getuid, Pid, User};
use thiserror::Error;
use snafu::{ensure, ResultExt, Snafu};

pub fn idmap(pid: Pid) -> Result<(), Error> {
let uid = getuid();
let gid = getgid();
let username = User::from_uid(uid)?.map(|user| user.name).unwrap_or_default();
let username = User::from_uid(uid)
.context(GetUserByUidSnafu)?
.map(|user| user.name)
.unwrap_or_default();

let subuid_mappings = load_sub_mappings(Kind::User, uid.as_raw(), &username)?;
let subgid_mappings = load_sub_mappings(Kind::Group, gid.as_raw(), &username)?;
Expand Down Expand Up @@ -64,12 +67,8 @@ fn load_sub_mappings(kind: Kind, id: u32, username: &str) -> Result<Vec<Submap>,

fn ensure_sub_count(kind: Kind, id: u32, mappings: &[Submap]) -> Result<(), Error> {
let count = mappings.iter().map(|map| map.count).sum::<u32>();

if count < 1000 {
Err(Error::SubMappingCount(id, kind, count))
} else {
Ok(())
}
ensure!(count >= 1000, SubMappingCountSnafu { id, kind, count });
Ok(())
}

fn format_id_mappings(sub_mappings: &[Submap]) -> Vec<Idmap> {
Expand Down Expand Up @@ -111,10 +110,14 @@ fn add_id_mappings(pid: Pid, kind: Kind, id: u32, mappings: &[Idmap]) -> Result<
]
}))
.output()
.map_err(|e| Error::Command(e.to_string(), kind))?;
.boxed()
.context(CommandSnafu { kind })?;

if !out.status.success() {
return Err(Error::Command(format!("{}", out.status), kind));
return Err(Error::Command {
kind,
source: out.status.to_string().into(),
});
}

Ok(())
Expand All @@ -133,12 +136,15 @@ struct Idmap {
count: u32,
}

#[derive(Debug, Error)]
#[derive(Debug, Snafu)]
pub enum Error {
#[error("\n\nAt least 1,000 sub{1} mappings are required for {1} {0}, found {2}\n\nMappings can be added to /etc/sub{1}")]
SubMappingCount(u32, Kind, u32),
#[error("new{1}map command failed: {0}")]
Command(String, Kind),
#[error("nix")]
Nix(#[from] nix::Error),
#[snafu(display("\n\nAt least 1,000 sub{kind} mappings are required for {kind} {id}, found {count}\n\nMappings can be added to /etc/sub{kind}"))]
SubMappingCount { id: u32, kind: Kind, count: u32 },
#[snafu(display("new{kind}map command failed"))]
Command {
kind: Kind,
source: Box<dyn std::error::Error + Send + Sync>,
},
#[snafu(display("get user by UID"))]
GetUserByUid { source: nix::Error },
}
Loading
Loading