1use std::fmt;
8
9use serde_json::Value;
10
11#[derive(Debug)]
13pub enum PulseError {
14 Auth { path: String, body: Option<Value> },
16 NotFound { path: String, body: Option<Value> },
18 Validation { path: String, body: Option<Value> },
20 RateLimit {
24 path: String,
25 body: Option<Value>,
26 retry_after_seconds: Option<u32>,
27 },
28 Api {
30 status: u16,
31 path: String,
32 body: Option<Value>,
33 },
34 Transport(reqwest::Error),
37 Json(serde_json::Error),
40 NoToken { path: String },
43 InvalidConfig(String),
45 Duplex(String),
48}
49
50impl PulseError {
51 pub fn is_auth_error(&self) -> bool {
52 matches!(self, PulseError::Auth { .. } | PulseError::NoToken { .. })
53 }
54
55 pub fn is_not_found(&self) -> bool {
56 matches!(self, PulseError::NotFound { .. })
57 }
58
59 pub fn is_validation_error(&self) -> bool {
60 matches!(self, PulseError::Validation { .. })
61 }
62
63 pub fn is_rate_limited(&self) -> bool {
64 matches!(self, PulseError::RateLimit { .. })
65 }
66
67 pub fn status_code(&self) -> Option<u16> {
70 match self {
71 PulseError::Auth { .. } | PulseError::NoToken { .. } => Some(401),
72 PulseError::NotFound { .. } => Some(404),
73 PulseError::Validation { .. } => Some(400),
74 PulseError::RateLimit { .. } => Some(429),
75 PulseError::Api { status, .. } => Some(*status),
76 PulseError::Transport(_)
77 | PulseError::Json(_)
78 | PulseError::InvalidConfig(_)
79 | PulseError::Duplex(_) => None,
80 }
81 }
82
83 pub fn body(&self) -> Option<&Value> {
85 match self {
86 PulseError::Auth { body, .. }
87 | PulseError::NotFound { body, .. }
88 | PulseError::Validation { body, .. }
89 | PulseError::RateLimit { body, .. }
90 | PulseError::Api { body, .. } => body.as_ref(),
91 _ => None,
92 }
93 }
94
95 pub fn path(&self) -> Option<&str> {
96 match self {
97 PulseError::Auth { path, .. }
98 | PulseError::NotFound { path, .. }
99 | PulseError::Validation { path, .. }
100 | PulseError::RateLimit { path, .. }
101 | PulseError::Api { path, .. }
102 | PulseError::NoToken { path } => Some(path),
103 _ => None,
104 }
105 }
106}
107
108impl fmt::Display for PulseError {
109 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
110 let summary = match self {
111 PulseError::Auth { path, body } => format_http(401, path, body.as_ref()),
112 PulseError::NotFound { path, body } => format_http(404, path, body.as_ref()),
113 PulseError::Validation { path, body } => format_http(400, path, body.as_ref()),
114 PulseError::RateLimit { path, body, .. } => format_http(429, path, body.as_ref()),
115 PulseError::Api { status, path, body } => format_http(*status, path, body.as_ref()),
116 PulseError::Transport(e) => return write!(f, "pulse: HTTP transport failure — {e}"),
117 PulseError::Json(e) => return write!(f, "pulse: JSON encode/decode failure — {e}"),
118 PulseError::NoToken { path } => {
119 return write!(
120 f,
121 "pulse: no token set for {path} — call client.auth().login(...).await first \
122 or pass .token(...) to the builder"
123 );
124 }
125 PulseError::InvalidConfig(msg) => return write!(f, "pulse: invalid config — {msg}"),
126 PulseError::Duplex(msg) => return write!(f, "pulse: duplex channel failure — {msg}"),
127 };
128 write!(f, "{summary}")
129 }
130}
131
132fn format_http(status: u16, path: &str, body: Option<&Value>) -> String {
133 let mut msg = format!("pulse: HTTP {status} from {path}");
134 if let Some(v) = body {
135 if let Some(err) = v
136 .get("error")
137 .and_then(Value::as_str)
138 .or_else(|| v.get("errorMessage").and_then(Value::as_str))
139 .or_else(|| v.get("message").and_then(Value::as_str))
140 {
141 msg.push_str(" — ");
142 msg.push_str(err);
143 }
144 }
145 msg
146}
147
148impl std::error::Error for PulseError {
149 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
150 match self {
151 PulseError::Transport(e) => Some(e),
152 PulseError::Json(e) => Some(e),
153 _ => None,
154 }
155 }
156}
157
158impl From<reqwest::Error> for PulseError {
159 fn from(e: reqwest::Error) -> Self {
160 PulseError::Transport(e)
161 }
162}
163
164impl From<serde_json::Error> for PulseError {
165 fn from(e: serde_json::Error) -> Self {
166 PulseError::Json(e)
167 }
168}