From b2eeec5000cf5ce7d26e71fdb81b3bbe7accc649 Mon Sep 17 00:00:00 2001 From: Matan Zruya Date: Sat, 1 Apr 2023 15:44:56 -0400 Subject: [PATCH 1/4] use error-stack --- Cargo.lock | 136 +++++++++---------------------------------------- Cargo.toml | 3 +- src/main.rs | 64 ++++++++++++++--------- src/project.rs | 45 ++++++++++++---- 4 files changed, 101 insertions(+), 147 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d16f578..b95f982 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,21 +2,6 @@ # It is not intended for manual editing. version = 3 -[[package]] -name = "addr2line" -version = "0.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a76fd60b23679b7d19bd066031410fb7e458ccc5e958eb5c325888ce4baedc97" -dependencies = [ - "gimli", -] - -[[package]] -name = "adler" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" - [[package]] name = "aho-corasick" version = "0.7.20" @@ -66,6 +51,12 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "anyhow" +version = "1.0.70" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7de8ce5e0f9f8d88245311066a578d72b7af3e7088f32783804676302df237e4" + [[package]] name = "assert_cmd" version = "2.0.10" @@ -87,21 +78,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" -[[package]] -name = "backtrace" -version = "0.3.67" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "233d376d6d185f2a3093e58f283f60f880315b6c60075b01f36b3b85154564ca" -dependencies = [ - "addr2line", - "cc", - "cfg-if", - "libc", - "miniz_oxide", - "object", - "rustc-demangle", -] - [[package]] name = "bitflags" version = "1.3.2" @@ -190,7 +166,7 @@ dependencies = [ "assert_cmd", "clap", "clap_derive", - "color-eyre", + "error-stack", "glob-match", "itertools", "jwalk", @@ -204,33 +180,6 @@ dependencies = [ "tracing-subscriber", ] -[[package]] -name = "color-eyre" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a667583cca8c4f8436db8de46ea8233c42a7d9ae424a82d338f2e4675229204" -dependencies = [ - "backtrace", - "color-spantrace", - "eyre", - "indenter", - "once_cell", - "owo-colors", - "tracing-error", -] - -[[package]] -name = "color-spantrace" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ba75b3d9449ecdccb27ecbc479fdc0b87fa2dd43d2f8298f9bf0e59aacc8dce" -dependencies = [ - "once_cell", - "owo-colors", - "tracing-core", - "tracing-error", -] - [[package]] name = "concolor-override" version = "1.0.0" @@ -363,13 +312,13 @@ dependencies = [ ] [[package]] -name = "eyre" -version = "0.6.8" +name = "error-stack" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c2b6b5a29c02cdc822728b7d7b8ae1bab3e3b05d44522770ddd49722eeac7eb" +checksum = "5f00447f331c7f726db5b8532ebc9163519eed03c6d7c8b73c90b3ff5646ac85" dependencies = [ - "indenter", - "once_cell", + "anyhow", + "rustc_version", ] [[package]] @@ -387,12 +336,6 @@ dependencies = [ "unicode-width", ] -[[package]] -name = "gimli" -version = "0.27.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad0a93d233ebf96623465aad4046a8d3aa4da22d4f4beba5388838c8a434bbb4" - [[package]] name = "glob-match" version = "0.2.1" @@ -426,12 +369,6 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" -[[package]] -name = "indenter" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" - [[package]] name = "indexmap" version = "1.9.3" @@ -541,15 +478,6 @@ dependencies = [ "autocfg", ] -[[package]] -name = "miniz_oxide" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa" -dependencies = [ - "adler", -] - [[package]] name = "nias" version = "0.5.0" @@ -576,15 +504,6 @@ dependencies = [ "libc", ] -[[package]] -name = "object" -version = "0.30.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea86265d3d3dcb6a27fc51bd29a4bf387fae9d2986b823079d4986af253eb439" -dependencies = [ - "memchr", -] - [[package]] name = "once_cell" version = "1.17.1" @@ -597,12 +516,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" -[[package]] -name = "owo-colors" -version = "3.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" - [[package]] name = "path-clean" version = "1.0.1" @@ -710,10 +623,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] -name = "rustc-demangle" -version = "0.1.22" +name = "rustc_version" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4a36c42d1873f9a77c53bde094f9664d9891bc604a45b4798fd2c389ed12e5b" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] [[package]] name = "rustix" @@ -753,6 +669,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +[[package]] +name = "semver" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed" + [[package]] name = "serde" version = "1.0.159" @@ -887,16 +809,6 @@ dependencies = [ "valuable", ] -[[package]] -name = "tracing-error" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d686ec1c0f384b1277f097b2f279a2ecc11afe8c133c1aabf036a27cb4cd206e" -dependencies = [ - "tracing", - "tracing-subscriber", -] - [[package]] name = "tracing-log" version = "0.1.3" diff --git a/Cargo.toml b/Cargo.toml index e4067a8..980e6f2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,8 +9,7 @@ debug = true [dependencies] clap = { version = "4.2.1", features = ["derive"] } clap_derive = "4.2.0" -color-eyre = "0.6.2" - +error-stack = "0.3.1" glob-match = "0.2.1" itertools = "0.10.5" jwalk = "0.8.1" diff --git a/src/main.rs b/src/main.rs index 3c4c69d..2d20b0d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,8 +1,9 @@ -use color_eyre::{eyre::Context, Result}; -use ownership::{Ownership, ValidationErrors}; +use ownership::Ownership; use crate::project::Project; use clap::{Parser, Subcommand}; +use core::fmt; +use error_stack::{Context, IntoReport, Result, ResultExt}; use path_clean::PathClean; use std::{ fs::File, @@ -46,62 +47,77 @@ struct Args { } impl Args { - fn absolute_project_root(&self) -> Result { - self.project_root - .canonicalize() - .with_context(|| format!("Can't canonizalize {}", self.project_root.to_string_lossy())) + fn absolute_project_root(&self) -> Result { + self.project_root.canonicalize().into_report().change_context(Error::Io) } - fn absolute_config_path(&self) -> Result { + fn absolute_config_path(&self) -> Result { Ok(self.absolute_path(&self.config_path)?.clean()) } - fn absolute_codeowners_path(&self) -> Result { + fn absolute_codeowners_path(&self) -> Result { Ok(self.absolute_path(&self.codeowners_file_path)?.clean()) } - fn absolute_path(&self, path: &Path) -> Result { + fn absolute_path(&self, path: &Path) -> Result { Ok(self.absolute_project_root()?.join(path)) } } -fn main() -> Result<()> { - color_eyre::install()?; - install_logger(); - print_validation_errors_to_stdout(cli())?; +#[derive(Debug)] +enum Error { + CannotBuildProject, + Io, + ValidationFailed, +} - Ok(()) +impl fmt::Display for Error { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Error::CannotBuildProject => fmt.write_str("Error::CannotBuildProject"), + Error::Io => fmt.write_str("Error::Io"), + Error::ValidationFailed => fmt.write_str("Error::ValidationFailed"), + } + } } -fn cli() -> Result<()> { +impl Context for Error {} + +fn cli() -> Result<(), Error> { let args = Args::parse(); let config_path = args.absolute_config_path()?; let codeowners_file_path = args.absolute_codeowners_path()?; let project_root = args.absolute_project_root()?; - let config = - serde_yaml::from_reader(File::open(&config_path).with_context(|| format!("Can't open {}", config_path.to_string_lossy()))?)?; - let ownership = Ownership::build(Project::build(&project_root, &codeowners_file_path, &config)?); + let config_file = File::open(&config_path).into_report().change_context(Error::Io).attach_printable(format!("{}", config_path.to_string_lossy()))?; + let config = serde_yaml::from_reader(config_file).into_report().change_context(Error::Io)?; + + let ownership = + Ownership::build(Project::build(&project_root, &codeowners_file_path, &config).change_context(Error::CannotBuildProject)?); let command = args.command; match command { - Command::Validate => ownership.validate()?, + Command::Validate => ownership.validate().into_report().change_context(Error::ValidationFailed)?, Command::Generate => { - std::fs::write(codeowners_file_path, ownership.generate_file())?; + std::fs::write(codeowners_file_path, ownership.generate_file()) + .into_report() + .change_context(Error::Io)?; } Command::GenerateAndValidate => { - std::fs::write(codeowners_file_path, ownership.generate_file())?; - ownership.validate()? + std::fs::write(codeowners_file_path, ownership.generate_file()) + .into_report() + .change_context(Error::Io)?; + ownership.validate().into_report().change_context(Error::ValidationFailed)? } } Ok(()) } -fn print_validation_errors_to_stdout(result: Result<()>) -> Result<()> { +fn print_validation_errors_to_stdout(result: Result<(), Error>) -> Result<(), Error> { if let Err(error) = result { - if let Some(validation_errors) = error.downcast_ref::() { + if let Some(validation_errors) = error.downcast_ref::() { println!("{}", validation_errors); process::exit(-1); } else { diff --git a/src/project.rs b/src/project.rs index d4db98e..538d23f 100644 --- a/src/project.rs +++ b/src/project.rs @@ -1,3 +1,4 @@ +use core::fmt; use std::{ collections::HashMap, fs::File, @@ -5,7 +6,8 @@ use std::{ path::{Path, PathBuf}, }; -use color_eyre::Result; +use error_stack::{Context, IntoReport, Result, ResultExt}; + use jwalk::WalkDir; use rayon::prelude::{IntoParallelIterator, ParallelIterator}; use regex::Regex; @@ -109,9 +111,26 @@ mod deserializers { } } +#[derive(Debug)] +pub enum Error { + Io, + SerdeYaml, +} + +impl fmt::Display for Error { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Error::Io => fmt.write_str("Error::Io"), + Error::SerdeYaml => fmt.write_str("Error::SerdeYaml"), + } + } +} + +impl Context for Error {} + impl Project { #[instrument(level = "debug", skip_all)] - pub fn build(base_path: &Path, codeowners_file_path: &Path, config: &Config) -> Result { + pub fn build(base_path: &Path, codeowners_file_path: &Path, config: &Config) -> Result { debug!("scanning project ({})", base_path.to_string_lossy()); let mut owned_file_paths: Vec = Vec::new(); @@ -120,10 +139,14 @@ impl Project { let mut vendored_gems: Vec = Vec::new(); for entry in WalkDir::new(base_path) { - let entry = entry?; + let entry = entry.into_report().change_context(Error::Io)?; let absolute_path = entry.path(); - let relative_path = absolute_path.strip_prefix(base_path)?.to_owned(); + let relative_path = absolute_path + .strip_prefix(base_path) + .into_report() + .change_context(Error::Io)? + .to_owned(); if entry.file_type().is_dir() { if relative_path.parent() == Some(Path::new(&config.vendored_gems_path)) { @@ -160,7 +183,8 @@ impl Project { } if matches_globs(&relative_path, &config.team_file_glob) { - let deserializer: deserializers::Team = serde_yaml::from_reader(File::open(&absolute_path)?)?; + let file = File::open(&absolute_path).into_report().change_context(Error::Io)?; + let deserializer: deserializers::Team = serde_yaml::from_reader(file).into_report().change_context(Error::SerdeYaml)?; teams.push(Team { path: absolute_path.clone(), @@ -186,7 +210,9 @@ impl Project { ); let codeowners_file: String = if codeowners_file_path.exists() { - std::fs::read_to_string(codeowners_file_path)? + std::fs::read_to_string(codeowners_file_path) + .into_report() + .change_context(Error::Io)? } else { "".to_owned() }; @@ -245,7 +271,7 @@ fn owned_files(owned_file_paths: Vec) -> Vec { .into_par_iter() .map(|path| { let file = File::open(&path).unwrap_or_else(|_| panic!("Couldn't open {}", path.to_string_lossy())); - let first_line: Result, std::io::Error> = std::io::BufReader::new(file).lines().next().transpose(); + let first_line = std::io::BufReader::new(file).lines().next().transpose(); let first_line = first_line.expect("error reading first line"); if first_line.is_none() { @@ -272,8 +298,9 @@ fn owned_files(owned_file_paths: Vec) -> Vec { .collect() } -fn package_owner(path: &Path) -> Result> { - let deserializer: deserializers::Package = serde_yaml::from_reader(File::open(path)?)?; +fn package_owner(path: &Path) -> Result, Error> { + let file = File::open(path).into_report().change_context(Error::Io)?; + let deserializer: deserializers::Package = serde_yaml::from_reader(file).into_report().change_context(Error::SerdeYaml)?; if let Some(metadata) = deserializer.metadata { Ok(metadata.owner) From 654c96bfa6384c6cd4fe15b9caddbb2a556c07e8 Mon Sep 17 00:00:00 2001 From: Matan Zruya Date: Sat, 1 Apr 2023 15:48:44 -0400 Subject: [PATCH 2/4] rebase --- src/main.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index 2d20b0d..9e26b7a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -83,6 +83,13 @@ impl fmt::Display for Error { impl Context for Error {} +fn main() -> Result<(), Error> { + install_logger(); + print_validation_errors_to_stdout(cli())?; + + Ok(()) +} + fn cli() -> Result<(), Error> { let args = Args::parse(); @@ -90,7 +97,10 @@ fn cli() -> Result<(), Error> { let codeowners_file_path = args.absolute_codeowners_path()?; let project_root = args.absolute_project_root()?; - let config_file = File::open(&config_path).into_report().change_context(Error::Io).attach_printable(format!("{}", config_path.to_string_lossy()))?; + let config_file = File::open(&config_path) + .into_report() + .change_context(Error::Io) + .attach_printable(format!("{}", config_path.to_string_lossy()))?; let config = serde_yaml::from_reader(config_file).into_report().change_context(Error::Io)?; let ownership = From af3fcf92247e26810d5de9d42f2f4ae4b2b9a795 Mon Sep 17 00:00:00 2001 From: Matan Zruya Date: Sat, 1 Apr 2023 16:00:48 -0400 Subject: [PATCH 3/4] Tidy validator error struct names --- src/main.rs | 10 ++++------ src/ownership.rs | 4 ++-- src/ownership/validator.rs | 39 +++++++++++++++++++------------------- 3 files changed, 25 insertions(+), 28 deletions(-) diff --git a/src/main.rs b/src/main.rs index 9e26b7a..8651c3b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -65,8 +65,7 @@ impl Args { } #[derive(Debug)] -enum Error { - CannotBuildProject, +pub enum Error { Io, ValidationFailed, } @@ -74,7 +73,6 @@ enum Error { impl fmt::Display for Error { fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - Error::CannotBuildProject => fmt.write_str("Error::CannotBuildProject"), Error::Io => fmt.write_str("Error::Io"), Error::ValidationFailed => fmt.write_str("Error::ValidationFailed"), } @@ -101,10 +99,10 @@ fn cli() -> Result<(), Error> { .into_report() .change_context(Error::Io) .attach_printable(format!("{}", config_path.to_string_lossy()))?; + let config = serde_yaml::from_reader(config_file).into_report().change_context(Error::Io)?; - let ownership = - Ownership::build(Project::build(&project_root, &codeowners_file_path, &config).change_context(Error::CannotBuildProject)?); + let ownership = Ownership::build(Project::build(&project_root, &codeowners_file_path, &config).change_context(Error::Io)?); let command = args.command; match command { @@ -127,7 +125,7 @@ fn cli() -> Result<(), Error> { fn print_validation_errors_to_stdout(result: Result<(), Error>) -> Result<(), Error> { if let Err(error) = result { - if let Some(validation_errors) = error.downcast_ref::() { + if let Some(validation_errors) = error.downcast_ref::() { println!("{}", validation_errors); process::exit(-1); } else { diff --git a/src/ownership.rs b/src/ownership.rs index 3e3a4e8..d81bfcb 100644 --- a/src/ownership.rs +++ b/src/ownership.rs @@ -10,7 +10,7 @@ mod tests; use crate::project::Project; -pub use validator::ValidationErrors; +pub use validator::Errors as ValidatorErrors; use self::{ file_generator::FileGenerator, @@ -40,7 +40,7 @@ impl Ownership { } #[instrument(level = "debug", skip_all)] - pub fn validate(&self) -> Result<(), ValidationErrors> { + pub fn validate(&self) -> Result<(), ValidatorErrors> { debug!("validating file ownership"); let validator = Validator { project: self.project.clone(), diff --git a/src/ownership/validator.rs b/src/ownership/validator.rs index 479004c..b35be6b 100644 --- a/src/ownership/validator.rs +++ b/src/ownership/validator.rs @@ -1,6 +1,5 @@ use core::fmt; use std::collections::HashMap; -use std::error::Error; use std::fmt::Display; use std::path::Path; @@ -31,18 +30,18 @@ struct Owner { } #[derive(Debug)] -enum ValidationError { +enum Error { FileWithoutOwner { path: PathBuf }, FileWithMultipleOwners { path: PathBuf, owners: Vec }, CodeownershipFileIsStale, } #[derive(Debug)] -pub struct ValidationErrors(Vec); +pub struct Errors(Vec); impl Validator { #[instrument(level = "debug", skip_all)] - pub fn validate(&self) -> Result<(), ValidationErrors> { + pub fn validate(&self) -> Result<(), Errors> { let mut validation_errors = Vec::new(); debug!("validate_file_ownership"); @@ -54,20 +53,20 @@ impl Validator { if validation_errors.is_empty() { Ok(()) } else { - Err(ValidationErrors(validation_errors)) + Err(Errors(validation_errors)) } } - fn validate_file_ownership(&self) -> Vec { + fn validate_file_ownership(&self) -> Vec { let mut validation_errors = Vec::new(); for (file, owners) in self.file_to_owners() { let relative_path = self.project.relative_path(&file.path).to_owned(); if owners.is_empty() { - validation_errors.push(ValidationError::FileWithoutOwner { path: relative_path }) + validation_errors.push(Error::FileWithoutOwner { path: relative_path }) } else if owners.len() > 1 { - validation_errors.push(ValidationError::FileWithMultipleOwners { + validation_errors.push(Error::FileWithMultipleOwners { path: relative_path, owners, }) @@ -77,11 +76,11 @@ impl Validator { validation_errors } - fn validate_codeowners_file(&self) -> Vec { + fn validate_codeowners_file(&self) -> Vec { let generated_file = self.file_generator.generate_file(); if generated_file != self.project.codeowners_file { - vec![ValidationError::CodeownershipFileIsStale] + vec![Error::CodeownershipFileIsStale] } else { vec![] } @@ -127,12 +126,12 @@ impl Validator { } } -impl ValidationError { +impl Error { pub fn error_category_message(&self) -> String { match self { - ValidationError::FileWithoutOwner { path: _ } => "Some files are missing ownership:".to_owned(), - ValidationError::FileWithMultipleOwners { path: _, owners: _ } => "Code ownership should only be defined for each file in one way. The following files have declared ownership in multiple ways.".to_owned(), - ValidationError::CodeownershipFileIsStale => { + Error::FileWithoutOwner { path: _ } => "Some files are missing ownership:".to_owned(), + Error::FileWithMultipleOwners { path: _, owners: _ } => "Code ownership should only be defined for each file in one way. The following files have declared ownership in multiple ways.".to_owned(), + Error::CodeownershipFileIsStale => { "CODEOWNERS out of date. Run `codeownership generate` to update the CODEOWNERS file".to_owned() } } @@ -140,8 +139,8 @@ impl ValidationError { pub fn error_message(&self) -> String { match self { - ValidationError::FileWithoutOwner { path } => format!("- {}", path.to_string_lossy()), - ValidationError::FileWithMultipleOwners { path, owners } => owners + Error::FileWithoutOwner { path } => format!("- {}", path.to_string_lossy()), + Error::FileWithMultipleOwners { path, owners } => owners .iter() .flat_map(|owner| { owner @@ -150,12 +149,12 @@ impl ValidationError { .map(|source| format!("- {} (owner: {}, source: {})", path.to_string_lossy(), owner.team_name, &source)) }) .join("\n"), - ValidationError::CodeownershipFileIsStale => "".to_owned(), + Error::CodeownershipFileIsStale => "".to_owned(), } } } -impl Display for ValidationErrors { +impl Display for Errors { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let grouped_errors = self.0.iter().into_group_map_by(|error| error.error_category_message()); for (error_category_message, errors) in grouped_errors { @@ -174,8 +173,8 @@ impl Display for ValidationErrors { } } -impl Error for ValidationErrors { +impl std::error::Error for Errors { fn description(&self) -> &str { - "ValidationError" + "Error" } } From 5951099551335523e8010a0c12dbd692fc6e673f Mon Sep 17 00:00:00 2001 From: Matan Zruya Date: Sun, 2 Apr 2023 12:29:34 -0400 Subject: [PATCH 4/4] add extension trait to reduce repetitive error transformation boilerplate --- src/ext.rs | 30 ++++++++++++++++++++++++++++++ src/main.rs | 23 ++++++++++------------- src/project.rs | 24 +++++++++--------------- 3 files changed, 49 insertions(+), 28 deletions(-) create mode 100644 src/ext.rs diff --git a/src/ext.rs b/src/ext.rs new file mode 100644 index 0000000..920485a --- /dev/null +++ b/src/ext.rs @@ -0,0 +1,30 @@ +use error_stack::{Context, IntoReport, Report, Result, ResultExt}; + +/// Extension trait to shorten repretitive calls, `into_report().change_context(NewError) => `into_context(NewError)` +pub trait IntoContext: Sized { + /// Type of the [`Ok`] value in the [`Result`] + type Ok; + + /// Type of the resulting [`Err`] variant wrapped inside a [`Report`]. + type Err; + + /// Converts the [`Err`] variant of the [`Result`] to a [`Report`] + fn into_context(self, context: C) -> Result + where + C: Context; +} + +impl IntoContext for core::result::Result +where + Report: From, +{ + type Err = E; + type Ok = T; + + fn into_context(self, context: C) -> Result + where + C: Context, + { + self.into_report().change_context(context) + } +} diff --git a/src/main.rs b/src/main.rs index 8651c3b..56e18d4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,9 +1,10 @@ +use ext::IntoContext; use ownership::Ownership; use crate::project::Project; use clap::{Parser, Subcommand}; use core::fmt; -use error_stack::{Context, IntoReport, Result, ResultExt}; +use error_stack::{Context, Result, ResultExt}; use path_clean::PathClean; use std::{ fs::File, @@ -12,6 +13,7 @@ use std::{ }; mod config; +mod ext; mod ownership; mod project; @@ -48,7 +50,7 @@ struct Args { impl Args { fn absolute_project_root(&self) -> Result { - self.project_root.canonicalize().into_report().change_context(Error::Io) + self.project_root.canonicalize().into_context(Error::Io) } fn absolute_config_path(&self) -> Result { @@ -96,27 +98,22 @@ fn cli() -> Result<(), Error> { let project_root = args.absolute_project_root()?; let config_file = File::open(&config_path) - .into_report() - .change_context(Error::Io) + .into_context(Error::Io) .attach_printable(format!("{}", config_path.to_string_lossy()))?; - let config = serde_yaml::from_reader(config_file).into_report().change_context(Error::Io)?; + let config = serde_yaml::from_reader(config_file).into_context(Error::Io)?; let ownership = Ownership::build(Project::build(&project_root, &codeowners_file_path, &config).change_context(Error::Io)?); let command = args.command; match command { - Command::Validate => ownership.validate().into_report().change_context(Error::ValidationFailed)?, + Command::Validate => ownership.validate().into_context(Error::ValidationFailed)?, Command::Generate => { - std::fs::write(codeowners_file_path, ownership.generate_file()) - .into_report() - .change_context(Error::Io)?; + std::fs::write(codeowners_file_path, ownership.generate_file()).into_context(Error::Io)?; } Command::GenerateAndValidate => { - std::fs::write(codeowners_file_path, ownership.generate_file()) - .into_report() - .change_context(Error::Io)?; - ownership.validate().into_report().change_context(Error::ValidationFailed)? + std::fs::write(codeowners_file_path, ownership.generate_file()).into_context(Error::Io)?; + ownership.validate().into_context(Error::ValidationFailed)? } } diff --git a/src/project.rs b/src/project.rs index 538d23f..ab4c22f 100644 --- a/src/project.rs +++ b/src/project.rs @@ -6,14 +6,14 @@ use std::{ path::{Path, PathBuf}, }; -use error_stack::{Context, IntoReport, Result, ResultExt}; +use error_stack::{Context, Result}; use jwalk::WalkDir; use rayon::prelude::{IntoParallelIterator, ParallelIterator}; use regex::Regex; use tracing::{debug, instrument}; -use crate::config::Config; +use crate::{config::Config, ext::IntoContext}; use glob_match::glob_match; pub struct Project { @@ -139,14 +139,10 @@ impl Project { let mut vendored_gems: Vec = Vec::new(); for entry in WalkDir::new(base_path) { - let entry = entry.into_report().change_context(Error::Io)?; + let entry = entry.into_context(Error::Io)?; let absolute_path = entry.path(); - let relative_path = absolute_path - .strip_prefix(base_path) - .into_report() - .change_context(Error::Io)? - .to_owned(); + let relative_path = absolute_path.strip_prefix(base_path).into_context(Error::Io)?.to_owned(); if entry.file_type().is_dir() { if relative_path.parent() == Some(Path::new(&config.vendored_gems_path)) { @@ -183,8 +179,8 @@ impl Project { } if matches_globs(&relative_path, &config.team_file_glob) { - let file = File::open(&absolute_path).into_report().change_context(Error::Io)?; - let deserializer: deserializers::Team = serde_yaml::from_reader(file).into_report().change_context(Error::SerdeYaml)?; + let file = File::open(&absolute_path).into_context(Error::Io)?; + let deserializer: deserializers::Team = serde_yaml::from_reader(file).into_context(Error::SerdeYaml)?; teams.push(Team { path: absolute_path.clone(), @@ -210,9 +206,7 @@ impl Project { ); let codeowners_file: String = if codeowners_file_path.exists() { - std::fs::read_to_string(codeowners_file_path) - .into_report() - .change_context(Error::Io)? + std::fs::read_to_string(codeowners_file_path).into_context(Error::Io)? } else { "".to_owned() }; @@ -299,8 +293,8 @@ fn owned_files(owned_file_paths: Vec) -> Vec { } fn package_owner(path: &Path) -> Result, Error> { - let file = File::open(path).into_report().change_context(Error::Io)?; - let deserializer: deserializers::Package = serde_yaml::from_reader(file).into_report().change_context(Error::SerdeYaml)?; + let file = File::open(path).into_context(Error::Io)?; + let deserializer: deserializers::Package = serde_yaml::from_reader(file).into_context(Error::SerdeYaml)?; if let Some(metadata) = deserializer.metadata { Ok(metadata.owner)