Skip to main content

hopr_transport/path/
errors.rs

1use std::sync::Arc;
2
3use hopr_api::types::internal::errors::PathError;
4
5pub type Result<T> = std::result::Result<T, PathPlannerError>;
6
7/// Errors produced by the path planner and graph-based path selector.
8#[derive(thiserror::Error, Debug)]
9pub enum PathPlannerError {
10    #[error("path error: {0}")]
11    Path(#[from] PathError),
12
13    #[error("{0}")]
14    Other(#[from] anyhow::Error),
15
16    #[error("surb: {0}")]
17    Surb(String),
18
19    #[error("api: {0}")]
20    Api(String),
21
22    #[error("cache error: {0}")]
23    CacheError(#[from] Arc<Self>),
24}
25
26impl PathPlannerError {
27    /// Returns `true` if this error is a SURB-starvation error, including one wrapped by the
28    /// path-cache layer as [`CacheError`](PathPlannerError::CacheError). Callers that retry on
29    /// transient SURB exhaustion must use this rather than matching [`Surb`](PathPlannerError::Surb)
30    /// directly, otherwise a cache-wrapped SURB error is misclassified as a hard failure.
31    pub fn is_surb(&self) -> bool {
32        match self {
33            PathPlannerError::Surb(_) => true,
34            PathPlannerError::CacheError(inner) => inner.is_surb(),
35            _ => false,
36        }
37    }
38}
39
40#[cfg(test)]
41mod tests {
42    use super::*;
43
44    #[test]
45    fn is_surb_detects_direct_and_cache_wrapped() {
46        let direct = PathPlannerError::Surb("no surb".into());
47        assert!(direct.is_surb(), "direct Surb must be detected");
48
49        // A SURB error surfaced through the path cache arrives wrapped; the old direct-only match
50        // would misclassify this as a hard failure and skip the retry.
51        let wrapped = PathPlannerError::CacheError(Arc::new(PathPlannerError::Surb("no surb".into())));
52        assert!(wrapped.is_surb(), "cache-wrapped Surb must be detected");
53
54        let nested = PathPlannerError::CacheError(Arc::new(wrapped));
55        assert!(nested.is_surb(), "doubly cache-wrapped Surb must be detected");
56
57        let other = PathPlannerError::Api("unrelated".into());
58        assert!(
59            !other.is_surb(),
60            "non-Surb error must not be treated as SURB starvation"
61        );
62        assert!(
63            !PathPlannerError::CacheError(Arc::new(other)).is_surb(),
64            "cache-wrapped non-Surb error must not be treated as SURB starvation"
65        );
66    }
67}