1use std::io;
4use thiserror::Error;
5
6pub type Result<T> = std::result::Result<T, BraidError>;
8
9#[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 #[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 #[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}