Skip to main content

braid_core/core/
error.rs

1//! Error types for Braid HTTP operations.
2
3use std::io;
4use thiserror::Error;
5
6/// Result type for Braid HTTP operations.
7pub type Result<T> = std::result::Result<T, BraidError>;
8
9/// Errors that can occur during Braid HTTP operations.
10#[derive(Error, Debug)]
11#[non_exhaustive]
12pub enum BraidError {
13    #[error("HTTP error: {0}")]
14    Http(String),
15
16    #[error("I/O error: {0}")]
17    Io(#[from] io::Error),
18
19    #[error("Header parse error: {0}")]
20    HeaderParse(String),
21
22    #[error("Body parse error: {0}")]
23    BodyParse(String),
24
25    #[error("Invalid version: {0}")]
26    InvalidVersion(String),
27
28    #[error("Subscription error: {0}")]
29    Subscription(String),
30
31    #[error("JSON error: {0}")]
32    Json(#[from] serde_json::Error),
33
34    #[error("Subscription closed")]
35    SubscriptionClosed,
36
37    #[error("Expected status 209 for subscription, got {0}")]
38    InvalidSubscriptionStatus(u16),
39
40    #[error("Protocol error: {0}")]
41    Protocol(String),
42
43    #[error("Operation timed out")]
44    Timeout,
45
46    #[error("Request aborted")]
47    Aborted,
48
49    #[error("BraidFS Error: {0}")]
50    Fs(String),
51
52    #[error("Invalid UTF-8: {0}")]
53    InvalidUtf8(#[from] std::string::FromUtf8Error),
54
55    #[error("Configuration error: {0}")]
56    Config(String),
57
58    #[error("Internal error: {0}")]
59    Internal(String),
60
61    #[error("Server has dropped history")]
62    HistoryDropped,
63
64    #[error("Conflicting versions in merge: {0}")]
65    MergeConflict(String),
66
67    #[cfg(all(feature = "native", not(target_arch = "wasm32")))]
68    #[error("Notify error: {0}")]
69    Notify(#[from] notify::Error),
70
71    #[error("Anyhow error: {0}")]
72    Anyhow(String),
73
74    #[error("Client error: {0}")]
75    Client(#[from] braid_http::BraidError),
76}
77
78impl From<anyhow::Error> for BraidError {
79    fn from(err: anyhow::Error) -> Self {
80        BraidError::Anyhow(err.to_string())
81    }
82}
83
84impl BraidError {
85    /// Check if this error is retryable.
86    #[inline]
87    #[must_use]
88    pub fn is_retryable(&self) -> bool {
89        match self {
90            BraidError::Http(msg) => {
91                msg.contains("408")
92                    || msg.contains("425")
93                    || msg.contains("429")
94                    || msg.contains("502")
95                    || msg.contains("503")
96                    || msg.contains("504")
97            }
98            BraidError::Timeout | BraidError::Io(_) => true,
99            BraidError::HistoryDropped => false,
100            _ => false,
101        }
102    }
103
104    /// Check if this is an access denied error.
105    #[inline]
106    #[must_use]
107    pub fn is_access_denied(&self) -> bool {
108        match self {
109            BraidError::Http(msg) => msg.contains("401") || msg.contains("403"),
110            _ => false,
111        }
112    }
113}
114
115#[cfg(test)]
116mod tests {
117    use super::*;
118
119    #[test]
120    fn test_timeout_is_retryable() {
121        assert!(BraidError::Timeout.is_retryable());
122    }
123
124    #[test]
125    fn test_history_dropped_not_retryable() {
126        assert!(!BraidError::HistoryDropped.is_retryable());
127    }
128
129    #[test]
130    fn test_http_503_is_retryable() {
131        let err = BraidError::Http("503 Service Unavailable".into());
132        assert!(err.is_retryable());
133    }
134
135    #[test]
136    fn test_access_denied_401() {
137        let err = BraidError::Http("401 Unauthorized".into());
138        assert!(err.is_access_denied());
139    }
140}