Skip to content

Commit 9eb1be7

Browse files
authored
Integrate error-stack for better error and stacktrace reporting (#4)
[error-stack](https://hash.dev/blog/announcing-error-stack) is a fairly recent Rust error reporting library meant to build richer and more concise error reports. Using it adds boilerplate and forces explicit error types across all fallible methods. The result is a much more concise and clear terminal output and the flexibility to customize error reporting by targeting an error happening deep inside the stack. For example: <img width="870" alt="image" src="https://user-images.githubusercontent.com/653256/229308111-28957fe5-9ea0-41c0-847c-d37df9598071.png"> Or: <img width="1063" alt="image" src="https://user-images.githubusercontent.com/653256/229308123-27939b39-ca46-40da-9452-261b8956c89a.png">
1 parent 76b7343 commit 9eb1be7

File tree

7 files changed

+149
-167
lines changed

7 files changed

+149
-167
lines changed

Cargo.lock

Lines changed: 24 additions & 112 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,7 @@ debug = true
99
[dependencies]
1010
clap = { version = "4.2.1", features = ["derive"] }
1111
clap_derive = "4.2.0"
12-
color-eyre = "0.6.2"
13-
12+
error-stack = "0.3.1"
1413
glob-match = "0.2.1"
1514
itertools = "0.10.5"
1615
jwalk = "0.8.1"

src/ext.rs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
use error_stack::{Context, IntoReport, Report, Result, ResultExt};
2+
3+
/// Extension trait to shorten repretitive calls, `into_report().change_context(NewError) => `into_context(NewError)`
4+
pub trait IntoContext: Sized {
5+
/// Type of the [`Ok`] value in the [`Result`]
6+
type Ok;
7+
8+
/// Type of the resulting [`Err`] variant wrapped inside a [`Report<E>`].
9+
type Err;
10+
11+
/// Converts the [`Err`] variant of the [`Result`] to a [`Report<C>`]
12+
fn into_context<C>(self, context: C) -> Result<Self::Ok, C>
13+
where
14+
C: Context;
15+
}
16+
17+
impl<T, E> IntoContext for core::result::Result<T, E>
18+
where
19+
Report<E>: From<E>,
20+
{
21+
type Err = E;
22+
type Ok = T;
23+
24+
fn into_context<C>(self, context: C) -> Result<T, C>
25+
where
26+
C: Context,
27+
{
28+
self.into_report().change_context(context)
29+
}
30+
}

src/main.rs

Lines changed: 42 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1-
use color_eyre::{eyre::Context, Result};
2-
use ownership::{Ownership, ValidationErrors};
1+
use ext::IntoContext;
2+
use ownership::Ownership;
33

44
use crate::project::Project;
55
use clap::{Parser, Subcommand};
6+
use core::fmt;
7+
use error_stack::{Context, Result, ResultExt};
68
use path_clean::PathClean;
79
use std::{
810
fs::File,
@@ -11,6 +13,7 @@ use std::{
1113
};
1214

1315
mod config;
16+
mod ext;
1417
mod ownership;
1518
mod project;
1619

@@ -46,62 +49,80 @@ struct Args {
4649
}
4750

4851
impl Args {
49-
fn absolute_project_root(&self) -> Result<PathBuf> {
50-
self.project_root
51-
.canonicalize()
52-
.with_context(|| format!("Can't canonizalize {}", self.project_root.to_string_lossy()))
52+
fn absolute_project_root(&self) -> Result<PathBuf, Error> {
53+
self.project_root.canonicalize().into_context(Error::Io)
5354
}
5455

55-
fn absolute_config_path(&self) -> Result<PathBuf> {
56+
fn absolute_config_path(&self) -> Result<PathBuf, Error> {
5657
Ok(self.absolute_path(&self.config_path)?.clean())
5758
}
5859

59-
fn absolute_codeowners_path(&self) -> Result<PathBuf> {
60+
fn absolute_codeowners_path(&self) -> Result<PathBuf, Error> {
6061
Ok(self.absolute_path(&self.codeowners_file_path)?.clean())
6162
}
6263

63-
fn absolute_path(&self, path: &Path) -> Result<PathBuf> {
64+
fn absolute_path(&self, path: &Path) -> Result<PathBuf, Error> {
6465
Ok(self.absolute_project_root()?.join(path))
6566
}
6667
}
6768

68-
fn main() -> Result<()> {
69-
color_eyre::install()?;
69+
#[derive(Debug)]
70+
pub enum Error {
71+
Io,
72+
ValidationFailed,
73+
}
74+
75+
impl fmt::Display for Error {
76+
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
77+
match self {
78+
Error::Io => fmt.write_str("Error::Io"),
79+
Error::ValidationFailed => fmt.write_str("Error::ValidationFailed"),
80+
}
81+
}
82+
}
83+
84+
impl Context for Error {}
85+
86+
fn main() -> Result<(), Error> {
7087
install_logger();
7188
print_validation_errors_to_stdout(cli())?;
7289

7390
Ok(())
7491
}
7592

76-
fn cli() -> Result<()> {
93+
fn cli() -> Result<(), Error> {
7794
let args = Args::parse();
7895

7996
let config_path = args.absolute_config_path()?;
8097
let codeowners_file_path = args.absolute_codeowners_path()?;
8198
let project_root = args.absolute_project_root()?;
8299

83-
let config =
84-
serde_yaml::from_reader(File::open(&config_path).with_context(|| format!("Can't open {}", config_path.to_string_lossy()))?)?;
85-
let ownership = Ownership::build(Project::build(&project_root, &codeowners_file_path, &config)?);
100+
let config_file = File::open(&config_path)
101+
.into_context(Error::Io)
102+
.attach_printable(format!("{}", config_path.to_string_lossy()))?;
103+
104+
let config = serde_yaml::from_reader(config_file).into_context(Error::Io)?;
105+
106+
let ownership = Ownership::build(Project::build(&project_root, &codeowners_file_path, &config).change_context(Error::Io)?);
86107
let command = args.command;
87108

88109
match command {
89-
Command::Validate => ownership.validate()?,
110+
Command::Validate => ownership.validate().into_context(Error::ValidationFailed)?,
90111
Command::Generate => {
91-
std::fs::write(codeowners_file_path, ownership.generate_file())?;
112+
std::fs::write(codeowners_file_path, ownership.generate_file()).into_context(Error::Io)?;
92113
}
93114
Command::GenerateAndValidate => {
94-
std::fs::write(codeowners_file_path, ownership.generate_file())?;
95-
ownership.validate()?
115+
std::fs::write(codeowners_file_path, ownership.generate_file()).into_context(Error::Io)?;
116+
ownership.validate().into_context(Error::ValidationFailed)?
96117
}
97118
}
98119

99120
Ok(())
100121
}
101122

102-
fn print_validation_errors_to_stdout(result: Result<()>) -> Result<()> {
123+
fn print_validation_errors_to_stdout(result: Result<(), Error>) -> Result<(), Error> {
103124
if let Err(error) = result {
104-
if let Some(validation_errors) = error.downcast_ref::<ValidationErrors>() {
125+
if let Some(validation_errors) = error.downcast_ref::<ownership::ValidatorErrors>() {
105126
println!("{}", validation_errors);
106127
process::exit(-1);
107128
} else {

0 commit comments

Comments
 (0)