use std::time::Duration;
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum Error {
#[error("operation timed out after {0:?}")]
Timeout(Duration),
#[error("{0}")]
Other(String),
}
impl Error {
pub fn timeout(elapsed: Duration) -> zenoh::Error {
Box::new(Error::Timeout(elapsed))
}
}
pub fn is_timeout(err: &(dyn std::error::Error + 'static)) -> bool {
let mut current: Option<&(dyn std::error::Error + 'static)> = Some(err);
while let Some(e) = current {
if matches!(e.downcast_ref::<Error>(), Some(Error::Timeout(_))) {
return true;
}
current = e.source();
}
false
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn timeout_constructor_is_detected() {
let err = Error::timeout(Duration::from_millis(250));
assert!(is_timeout(&*err));
assert!(err.to_string().contains("250ms"));
}
#[test]
fn non_timeout_errors_are_not_flagged() {
let other = Error::Other("some other failure".to_string());
assert!(!is_timeout(&other));
let zenoh_err: zenoh::Error = zenoh::Error::from("plain failure");
assert!(!is_timeout(&*zenoh_err));
}
#[test]
fn is_timeout_walks_the_source_chain() {
#[derive(Debug, thiserror::Error)]
#[error("wrapper")]
struct Wrapper(#[source] Error);
let wrapped = Wrapper(Error::Timeout(Duration::from_secs(1)));
assert!(is_timeout(&wrapped));
let wrapped_other = Wrapper(Error::Other("nope".to_string()));
assert!(!is_timeout(&wrapped_other));
}
#[test]
fn downcast_recovers_the_variant() {
let err = Error::timeout(Duration::from_secs(3));
match err.downcast_ref::<Error>() {
Some(Error::Timeout(d)) => assert_eq!(*d, Duration::from_secs(3)),
other => panic!("expected Timeout, got {other:?}"),
}
}
}