From 41c0c42e763761775d7ca3663ba47783025b620c Mon Sep 17 00:00:00 2001 From: alexion Date: Thu, 17 Sep 2026 14:42:32 -0400 Subject: [PATCH] feat(monorepo): add status type --- Cargo.lock | 12 +++--- Cargo.toml | 2 + src/status/BUILD | 14 +++++++ src/status/Cargo.toml | 9 +++++ src/status/status.rs | 84 +++++++++++++++++++++++++++++++++++++++ src/status/status_test.rs | 67 +++++++++++++++++++++++++++++++ src/xatu/BUILD | 2 + src/xatu/Cargo.toml | 2 +- src/xatu/xatu.rs | 4 +- src/xatu/xatu_main.rs | 6 +-- 10 files changed, 188 insertions(+), 14 deletions(-) create mode 100644 src/status/BUILD create mode 100644 src/status/Cargo.toml create mode 100644 src/status/status.rs create mode 100644 src/status/status_test.rs diff --git a/Cargo.lock b/Cargo.lock index 73e6e7b..a950d58 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -52,12 +52,6 @@ dependencies = [ "windows-sys", ] -[[package]] -name = "anyhow" -version = "1.0.104" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" - [[package]] name = "ariadne" version = "0.1.0" @@ -213,6 +207,10 @@ dependencies = [ "zmij", ] +[[package]] +name = "status" +version = "0.1.0" + [[package]] name = "strsim" version = "0.11.1" @@ -261,9 +259,9 @@ dependencies = [ name = "xatu" version = "0.1.0" dependencies = [ - "anyhow", "clap", "monorepo", + "status", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 776d0a4..56624fb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,7 @@ members = [ "src/ariadne", "src/machoke", "src/monorepo", + "src/status", "src/xatu", ] resolver = "3" @@ -12,4 +13,5 @@ anyhow = "1.0" clap = "4" monorepo = { path = "src/monorepo" } serde = "1.0" +status = { path = "src/status" } serde_json = "1.0" diff --git a/src/status/BUILD b/src/status/BUILD new file mode 100644 index 0000000..d1b459e --- /dev/null +++ b/src/status/BUILD @@ -0,0 +1,14 @@ +load("@rules_rust//rust:defs.bzl", "rust_library", "rust_test") + +rust_library( + name = "status", + crate_name = "status", + srcs = ["status.rs"], + visibility = ["//visibility:public"], +) + +rust_test( + name = "status_test", + srcs = ["status_test.rs"], + deps = [":status"], +) diff --git a/src/status/Cargo.toml b/src/status/Cargo.toml new file mode 100644 index 0000000..9dcdf30 --- /dev/null +++ b/src/status/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "status" +version = "0.1.0" +edition = "2024" + +[lib] +path = "status.rs" + +[dependencies] diff --git a/src/status/status.rs b/src/status/status.rs new file mode 100644 index 0000000..2bca061 --- /dev/null +++ b/src/status/status.rs @@ -0,0 +1,84 @@ +pub type Result = std::result::Result; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Code { + InvalidArgument, + NotFound, + PermissionDenied, + Internal, +} + +#[derive(Debug)] +pub struct Status { + code: Code, + message: String, + source: Option>, +} + +impl Status { + pub fn new(code: Code, message: impl Into) -> Self { + Self { + code, + message: message.into(), + source: None, + } + } + + pub fn not_found(message: impl Into) -> Self { + Self::new(Code::NotFound, message) + } + + pub fn invalid_argument(message: impl Into) -> Self { + Self::new(Code::InvalidArgument, message) + } + + pub fn permission_denied(message: impl Into) -> Self { + Self::new(Code::PermissionDenied, message) + } + + pub fn internal(message: impl Into) -> Self { + Self::new(Code::Internal, message) + } + + pub fn with_source(mut self, source: E) -> Self + where + E: std::error::Error + Send + Sync + 'static, + { + self.source = Some(Box::new(source)); + self + } + + pub fn code(&self) -> Code { + self.code + } + + pub fn message(&self) -> &str { + &self.message + } +} + +impl std::fmt::Display for Status { + fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "{:?}: {}", self.code, self.message) + } +} + +impl std::error::Error for Status { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + self.source + .as_deref() + .map(|source| source as &(dyn std::error::Error + 'static)) + } +} + +impl From for Status { + fn from(error: std::io::Error) -> Self { + let code = match error.kind() { + std::io::ErrorKind::NotFound => Code::NotFound, + std::io::ErrorKind::PermissionDenied => Code::PermissionDenied, + _ => Code::Internal, + }; + + Self::new(code, error.to_string()).with_source(error) + } +} diff --git a/src/status/status_test.rs b/src/status/status_test.rs new file mode 100644 index 0000000..289b8e4 --- /dev/null +++ b/src/status/status_test.rs @@ -0,0 +1,67 @@ +use std::error::Error; + +use status::{Code, Status}; + +#[test] +fn constructors_set_code_and_message() { + let cases = [ + (Status::invalid_argument("bad input"), Code::InvalidArgument), + (Status::not_found("missing input"), Code::NotFound), + (Status::permission_denied("denied"), Code::PermissionDenied), + (Status::internal("failed"), Code::Internal), + ]; + + for (status, code) in cases { + assert_eq!(code, status.code()); + assert!(!status.message().is_empty()); + } +} + +#[test] +fn display_includes_code_and_message() { + let status = Status::not_found("missing input"); + + assert_eq!("NotFound: missing input", status.to_string()); +} + +#[test] +fn with_source_preserves_source_error() { + let status = Status::internal("read failed").with_source(std::io::Error::new( + std::io::ErrorKind::PermissionDenied, + "denied", + )); + + assert_eq!("denied", status.source().unwrap().to_string()); +} + +#[test] +fn io_error_maps_not_found() { + let status = Status::from(std::io::Error::new( + std::io::ErrorKind::NotFound, + "missing", + )); + + assert_eq!(Code::NotFound, status.code()); + assert_eq!("missing", status.message()); + assert_eq!("missing", status.source().unwrap().to_string()); +} + +#[test] +fn io_error_maps_permission_denied() { + let status = Status::from(std::io::Error::new( + std::io::ErrorKind::PermissionDenied, + "denied", + )); + + assert_eq!(Code::PermissionDenied, status.code()); +} + +#[test] +fn io_error_maps_other_errors_to_internal() { + let status = Status::from(std::io::Error::new( + std::io::ErrorKind::InvalidData, + "invalid data", + )); + + assert_eq!(Code::Internal, status.code()); +} diff --git a/src/xatu/BUILD b/src/xatu/BUILD index 7eb3699..ac03412 100644 --- a/src/xatu/BUILD +++ b/src/xatu/BUILD @@ -4,6 +4,7 @@ rust_library( name = "xatu_lib", crate_name = "xatu", srcs = ["xatu.rs"], + deps = ["//src/status:status"], ) rust_binary( @@ -13,6 +14,7 @@ rust_binary( deps = [ ":xatu_lib", "//src/monorepo:monorepo", + "//src/status:status", "//third_party/crates:clap", ], ) diff --git a/src/xatu/Cargo.toml b/src/xatu/Cargo.toml index 0cb65af..10d4d0b 100644 --- a/src/xatu/Cargo.toml +++ b/src/xatu/Cargo.toml @@ -11,6 +11,6 @@ name = "xatu" path = "xatu_main.rs" [dependencies] -anyhow = { workspace = true } clap = { workspace = true, features = ["derive"] } monorepo = { workspace = true } +status = { workspace = true } diff --git a/src/xatu/xatu.rs b/src/xatu/xatu.rs index ac8d3a5..353efa8 100644 --- a/src/xatu/xatu.rs +++ b/src/xatu/xatu.rs @@ -1,14 +1,12 @@ use std::path::PathBuf; -pub fn run(files: Vec) -> Result<()> { +pub fn run(files: Vec) -> status::Result<()> { for file in files { println!("{}", file.path.display()); } Ok(()) } -pub type Result = std::result::Result; - #[derive(Debug)] pub struct SourceFile { pub path: PathBuf, diff --git a/src/xatu/xatu_main.rs b/src/xatu/xatu_main.rs index 567dfe1..ed90128 100644 --- a/src/xatu/xatu_main.rs +++ b/src/xatu/xatu_main.rs @@ -25,7 +25,7 @@ fn is_excluded_dir(path: &Path) -> bool { }) } -fn collect_source_files(path: &Path) -> xatu::Result> { +fn collect_source_files(path: &Path) -> status::Result> { let metadata = std::fs::symlink_metadata(path)?; if metadata.file_type().is_dir() { @@ -59,7 +59,7 @@ struct Args { paths: Vec, } -fn main() -> xatu::Result<()> { +fn main() -> status::Result<()> { let args = Args::parse(); let paths: Vec = args @@ -71,7 +71,7 @@ fn main() -> xatu::Result<()> { let files: Vec = paths .iter() .map(|path| collect_source_files(path)) - .collect::>>()? + .collect::>>()? .into_iter() .flatten() .collect();