//! 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)"); } }