From 633b9f0194852774a5a9609c016c8e299daecd29 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 --- src/monorepo/BUILD | 7 ++++ src/monorepo/monorepo.rs | 1 + src/monorepo/status.rs | 84 +++++++++++++++++++++++++++++++++++++ src/monorepo/status_test.rs | 67 +++++++++++++++++++++++++++++ 4 files changed, 159 insertions(+) create mode 100644 src/monorepo/status.rs create mode 100644 src/monorepo/status_test.rs diff --git a/src/monorepo/BUILD b/src/monorepo/BUILD index 6170275..b725db7 100644 --- a/src/monorepo/BUILD +++ b/src/monorepo/BUILD @@ -5,6 +5,7 @@ rust_library( crate_name = "monorepo", srcs = [ "monorepo.rs", + "status.rs", "workspace.rs", ], visibility = ["//visibility:public"], @@ -15,3 +16,9 @@ rust_test( srcs = ["workspace_test.rs"], deps = [":monorepo"], ) + +rust_test( + name = "status_test", + srcs = ["status_test.rs"], + deps = [":monorepo"], +) diff --git a/src/monorepo/monorepo.rs b/src/monorepo/monorepo.rs index b91220b..12bb7e7 100644 --- a/src/monorepo/monorepo.rs +++ b/src/monorepo/monorepo.rs @@ -1 +1,2 @@ +pub mod status; pub mod workspace; diff --git a/src/monorepo/status.rs b/src/monorepo/status.rs new file mode 100644 index 0000000..2bca061 --- /dev/null +++ b/src/monorepo/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/monorepo/status_test.rs b/src/monorepo/status_test.rs new file mode 100644 index 0000000..a7475ca --- /dev/null +++ b/src/monorepo/status_test.rs @@ -0,0 +1,67 @@ +use std::error::Error; + +use monorepo::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()); +}