From 56e0c2841103242c9645065003077ce1a6ec6a17 Mon Sep 17 00:00:00 2001 From: Dominik Werder Date: Thu, 7 Nov 2024 20:41:56 +0100 Subject: [PATCH] Factored into separate crate --- .gitignore | 2 + Cargo.toml | 26 +++ src/lib.rs | 610 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 638 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.toml create mode 100644 src/lib.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1b72444 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/Cargo.lock +/target diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..9ec51a0 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "daqbuf-err" +version = "0.0.6" +authors = ["Dominik Werder "] +edition = "2021" + +[lib] +doctest = false + +[dependencies] +backtrace = "0.3.68" +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +serde_cbor = "0.11.2" +rmp-serde = "1.1.1" +async-channel = "1.9.0" +async_channel_2 = { package = "async-channel", version = "2.0.0" } +chrono = { version = "0.4.26", features = ["serde"] } +url = "2.4.0" +regex = "1.9.1" +http = "1.0.0" +thiserror = "=0.0.1" +anyhow = "1.0" + +[patch.crates-io] +thiserror = { git = "https://github.com/dominikwerder/thiserror.git", branch = "cstm" } diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..2c1ff36 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,610 @@ +//! Error handling and reporting. + +#[macro_export] +macro_rules! err_dbg_dis { + ($tt:ty, $nn:expr) => { + impl ::core::fmt::Display for $tt { + fn fmt(&self, fmt: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(fmt, "{}::{:?}", $nn, self) + } + } + }; +} + +pub use anyhow; +pub use thiserror; +pub use thiserror::Error as ThisError; +// pub use thiserror::UserErrorClass; +// pub use thiserror::UserErrorContent; + +pub mod bt { + pub use backtrace::Backtrace; +} + +use serde::Deserialize; +use serde::Serialize; +use std::array::TryFromSliceError; +use std::convert::Infallible; +use std::fmt; +use std::net::AddrParseError; +use std::num::ParseFloatError; +use std::num::ParseIntError; +use std::string::FromUtf8Error; +use std::sync::PoisonError; + +pub type Res2 = anyhow::Result; + +#[derive(Debug, ThisError)] +pub enum ErrA { + #[error("bad-A")] + Bad, +} + +#[derive(Debug, ThisError)] +pub enum ErrB { + #[error("worse-B")] + Worse, + #[error("FromArrA")] + ErrA(#[from] ErrA), +} + +fn f_a() -> Result { + Err(ErrA::Bad) +} + +fn f_b() -> Result { + if true { + let res = f_a()?; + Ok(res) + } else { + Err(ErrB::Worse) + } +} + +#[allow(unused)] +fn f_c() -> Result { + return Ok(f_b()?); +} + +#[test] +fn test_fc() { + assert!(f_c().is_err()); +} + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub enum Reason { + InternalError, + BadRequest, + IoError, +} + +/// The common error type for this application. +#[derive(Clone, PartialEq, Serialize, Deserialize)] +pub struct Error { + msg: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + trace_str: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + public_msg: Option>, + #[serde(default, skip_serializing_if = "Option::is_none")] + reason: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + parent: Option>, +} + +impl Error { + pub fn with_msg_no_trace>(s: S) -> Self { + Self { + msg: s.into(), + trace_str: None, + public_msg: None, + reason: None, + parent: None, + } + } + + pub fn with_msg>(s: S) -> Self { + Self::with_msg_no_trace(s).add_backtrace() + } + + pub fn with_public_msg_no_trace>(s: S) -> Self { + let s = s.into(); + let ret = Self::with_msg_no_trace(&s); + let ret = ret.add_public_msg(s); + ret + } + + pub fn with_public_msg>(s: S) -> Self { + let s = s.into(); + let ret = Self::with_msg_no_trace(String::new()); + let ret = ret.add_backtrace(); + let ret = ret.add_public_msg(s); + ret + } + + pub fn from_string(e: E) -> Self + where + E: ToString, + { + Self::with_msg_no_trace(e.to_string()) + } + + pub fn add_backtrace(mut self) -> Self { + self.msg.extend(" (add_backtrace DISABLED)".chars()); + // ret.trace_str = Some(fmt_backtrace(&backtrace::Backtrace::new())); + self + } + + pub fn mark_bad_request(mut self) -> Self { + self.reason = Some(Reason::BadRequest); + self + } + + pub fn mark_io_error(mut self) -> Self { + self.reason = Some(Reason::IoError); + self + } + + pub fn add_public_msg(mut self, msg: impl Into) -> Self { + if self.public_msg.is_none() { + self.public_msg = Some(Vec::new()); + } + self.public_msg.as_mut().unwrap().push(msg.into()); + self + } + + pub fn msg(&self) -> &str { + &self.msg + } + + pub fn public_msg(&self) -> Option<&Vec> { + self.public_msg.as_ref() + } + + pub fn reason(&self) -> Option { + self.reason.clone() + } +} + +#[allow(unused)] +fn fmt_backtrace(trace: &backtrace::Backtrace) -> String { + if true { + return String::from("fmt_backtrace DISABLED"); + } + use std::io::Write; + let mut buf = Vec::new(); + let mut c1 = 0; + 'outer: for fr in trace.frames() { + for sy in fr.symbols() { + let is_ours = match sy.filename() { + None => false, + Some(s) => { + let s = s.to_str().unwrap(); + s.contains("/dev/daqbuffer/") || s.contains("/build/daqbuffer/") + } + }; + let name = match sy.name() { + Some(k) => k.to_string(), + _ => "[err]".into(), + }; + let filename = match sy.filename() { + Some(k) => match k.to_str() { + Some(k) => k, + _ => "[err]", + }, + _ => "[err]", + }; + let lineno = match sy.lineno() { + Some(k) => k, + _ => 0, + }; + if is_ours { + write!(&mut buf, "\n {name}\n {filename} {lineno}").unwrap(); + c1 += 1; + if c1 >= 10 { + break 'outer; + } + } + } + } + String::from_utf8(buf).unwrap() +} + +impl fmt::Debug for Error { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + let trace_str = if let Some(s) = &self.trace_str { + s.into() + } else { + String::new() + }; + write!(fmt, "msg: {}", self.msg)?; + if let Some(msgs) = self.public_msg() { + for (i, msg) in msgs.iter().enumerate() { + write!(fmt, "; pub({i}): {msg}")?; + } + } + if !trace_str.is_empty() { + write!(fmt, "\nTrace:\n{}", trace_str)?; + } + Ok(()) + } +} + +impl fmt::Display for Error { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + fmt::Debug::fmt(self, fmt) + } +} + +impl std::error::Error for Error {} + +pub trait ErrConv { + fn err_conv(self) -> Result; +} + +impl ErrConv for Result +where + E: Into, +{ + fn err_conv(self) -> Result { + match self { + Ok(k) => Ok(k), + Err(e) => Err(e.into()), + } + } +} + +pub trait ErrStr { + fn errstr(self) -> Result; +} + +impl ErrStr for Result +where + E: ToString, +{ + fn errstr(self) -> Result { + match self { + Ok(k) => Ok(k), + Err(e) => Err(Error::with_msg_no_trace(e.to_string())), + } + } +} + +pub trait ToErr { + fn to_err(self) -> Error; +} + +impl From for Error { + fn from(k: T) -> Self { + k.to_err() + } +} + +impl From for Error { + fn from(k: PublicError) -> Self { + Self { + msg: String::new(), + trace_str: None, + public_msg: Some(k.msg.clone()), + reason: k.reason(), + parent: None, + } + } +} + +impl ToErr for Infallible { + fn to_err(self) -> Error { + Error::with_msg_no_trace(String::new()) + } +} + +impl From for Error { + fn from(k: String) -> Self { + Self::from_string(k) + } +} + +impl From<&str> for Error { + fn from(k: &str) -> Self { + Self::from_string(k) + } +} + +impl From for Error { + fn from(k: std::io::Error) -> Self { + Self::from_string(k) + } +} + +impl From for Error { + fn from(k: AddrParseError) -> Self { + Self::from_string(k) + } +} + +impl From for Error { + fn from(k: serde_json::Error) -> Self { + Self::from_string(k) + } +} + +impl From> for Error { + fn from(k: async_channel::SendError) -> Self { + Self::from_string(k) + } +} + +impl From for Error { + fn from(k: async_channel::RecvError) -> Self { + Self::from_string(k) + } +} + +impl From> for Error { + fn from(k: async_channel_2::SendError) -> Self { + Self::from_string(k) + } +} + +impl From for Error { + fn from(k: async_channel_2::RecvError) -> Self { + Self::from_string(k) + } +} + +impl From for Error { + fn from(k: chrono::format::ParseError) -> Self { + Self::from_string(k) + } +} + +impl From for Error { + fn from(k: ParseIntError) -> Self { + Self::from_string(k) + } +} + +impl From for Error { + fn from(k: ParseFloatError) -> Self { + Self::from_string(k) + } +} + +impl From for Error { + fn from(k: FromUtf8Error) -> Self { + Self::from_string(k) + } +} + +impl From for Error { + fn from(k: std::str::Utf8Error) -> Self { + Self::from_string(k) + } +} + +impl From for Error { + fn from(k: serde_cbor::Error) -> Self { + Self::from_string(k) + } +} + +impl From for Error { + fn from(k: std::fmt::Error) -> Self { + Self::from_string(k) + } +} + +impl From for Error { + fn from(k: regex::Error) -> Self { + Self::from_string(k) + } +} + +impl From> for Error { + fn from(_: PoisonError) -> Self { + Self::from_string("PoisonError") + } +} + +impl From for Error { + fn from(k: url::ParseError) -> Self { + Self::from_string(format!("{:?}", k)) + } +} + +impl From for Error { + fn from(k: TryFromSliceError) -> Self { + Self::from_string(format!("{:?}", k)) + } +} + +impl From for Error { + fn from(k: rmp_serde::encode::Error) -> Self { + Self::from_string(format!("{:?}", k)) + } +} + +impl From for Error { + fn from(k: rmp_serde::decode::Error) -> Self { + Self::from_string(format!("{:?}", k)) + } +} + +impl From for Error { + fn from(k: anyhow::Error) -> Self { + Self::from_string(format!("{k}")) + } +} + +impl From for Error { + fn from(k: http::Error) -> Self { + Self::from_string(k) + } +} + +impl From for Error { + fn from(k: http::uri::InvalidUri) -> Self { + Self::from_string(k) + } +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct PublicError { + reason: Option, + msg: Vec, +} + +impl PublicError { + pub fn reason(&self) -> Option { + self.reason.clone() + } + + pub fn msg(&self) -> &Vec { + &self.msg + } +} + +impl From for PublicError { + fn from(value: String) -> Self { + Self { + reason: None, + msg: vec![value], + } + } +} + +impl From for PublicError { + fn from(k: Error) -> Self { + Self { + reason: k.reason(), + msg: k.public_msg().map(Clone::clone).unwrap_or(Vec::new()), + } + } +} + +impl From<&Error> for PublicError { + fn from(k: &Error) -> Self { + Self { + reason: k.reason(), + msg: vec![k.msg().into()], + } + } +} + +impl fmt::Display for PublicError { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + write!(fmt, "{:?}", self.msg) + } +} + +impl ToPublicError for Error { + fn to_public_error(&self) -> PublicError { + PublicError::from(self) + } +} + +pub fn todo() { + let bt = backtrace::Backtrace::new(); + eprintln!("TODO\n{bt:?}"); + todo!("TODO\n{bt:?}"); +} + +pub fn todoval() -> T { + let bt = backtrace::Backtrace::new(); + eprintln!("TODO\n{bt:?}"); + todo!("TODO todoval\n{bt:?}") +} + +pub trait ToPublicError: std::error::Error + Send { + fn to_public_error(&self) -> PublicError; +} + +#[cfg(test)] +mod test { + use super::*; + + #[derive(Debug, ThisError, Serialize, Deserialize)] + #[cstm(name = "SomeErrorEnumA")] + enum SomeErrorEnumA { + BadCase, + WithStringContent(String), + // #[error("bad: {0}")] + WithStringContentFmt(String), + } + + #[derive(Debug, ThisError, Serialize, Deserialize)] + #[cstm(name = "SomeErrorEnumB0")] + enum SomeErrorEnumB0 { + FromA(#[from] SomeErrorEnumA), + } + + #[derive(Debug, ThisError, Serialize, Deserialize)] + #[cstm(name = "SomeErrorEnumB1")] + enum SomeErrorEnumB1 { + FromA(#[from] SomeErrorEnumA), + #[error("caffe")] + Caffe(SomeErrorEnumA), + } + + fn failing_a_00() -> Result<(), SomeErrorEnumA> { + Err(SomeErrorEnumA::BadCase) + } + + fn failing_b0_00() -> Result<(), SomeErrorEnumB0> { + let ret = failing_a_00()?; + Ok(ret) + } + + fn failing_b1_00() -> Result<(), SomeErrorEnumB1> { + let ret = failing_a_00()?; + Ok(ret) + } + + #[test] + fn error_handle_a_00() { + assert_eq!(format!("{}", SomeErrorEnumA::BadCase), "SomeErrorEnumA::BadCase"); + } + + #[test] + fn error_handle_a_01() { + assert_eq!( + SomeErrorEnumA::WithStringContent(format!("inner")).to_string(), + "SomeErrorEnumA::WithStringContent" + ); + } + + #[test] + fn error_handle_a_02() { + assert_eq!( + SomeErrorEnumA::WithStringContentFmt(format!("inner failure \"quoted\"")).to_string(), + "bad: inner failure \"quoted\"" + ); + } + + #[test] + fn error_handle_b0_00() { + let e = failing_b0_00().unwrap_err(); + let s = e.to_string(); + assert_eq!(s, "SomeErrorEnumB0::FromA(SomeErrorEnumA::BadCase)"); + } + + #[test] + fn error_handle_b0_user_00() { + use thiserror::UserErrorClass; + use thiserror::UserErrorInfo; + let e = failing_b0_00().unwrap_err(); + let s = e.class(); + if let UserErrorClass::Unspecified = s { + () + } else { + panic!() + } + } + + #[test] + fn error_handle_b1_00() { + let e = failing_b1_00().unwrap_err(); + let s = e.to_string(); + assert_eq!(s, "SomeErrorEnumB1::FromA(SomeErrorEnumA::BadCase)"); + } +}