1use std::fmt;
29use synapse_proto::RpcStatus;
30
31pub type RpcResult<T, E = ServiceError> = Result<T, E>;
35
36#[derive(Debug, Clone)]
41pub struct ServiceError {
42 pub status: RpcStatus,
44 pub code: u32,
46 pub message: String,
48}
49
50impl ServiceError {
51 pub fn new(status: RpcStatus, code: u32, message: impl Into<String>) -> Self {
53 Self {
54 status,
55 code,
56 message: message.into(),
57 }
58 }
59
60 pub fn invalid_request(message: impl Into<String>) -> Self {
66 Self::new(RpcStatus::InvalidRequest, 400, message)
67 }
68
69 pub fn not_found(message: impl Into<String>) -> Self {
71 Self::new(RpcStatus::Error, 404, message)
72 }
73
74 pub fn unauthorized(message: impl Into<String>) -> Self {
76 Self::new(RpcStatus::Error, 401, message)
77 }
78
79 pub fn forbidden(message: impl Into<String>) -> Self {
81 Self::new(RpcStatus::Error, 403, message)
82 }
83
84 pub fn internal(message: impl Into<String>) -> Self {
86 Self::new(RpcStatus::Error, 500, message)
87 }
88
89 pub fn unavailable(message: impl Into<String>) -> Self {
91 Self::new(RpcStatus::Unavailable, 503, message)
92 }
93
94 pub fn timeout(message: impl Into<String>) -> Self {
96 Self::new(RpcStatus::Timeout, 504, message)
97 }
98
99 pub fn custom(code: u32, message: impl Into<String>) -> Self {
108 Self::new(RpcStatus::Error, code, message)
109 }
110
111 pub fn to_proto(&self) -> synapse_proto::RpcError {
113 synapse_proto::RpcError {
114 code: self.code,
115 message: self.message.clone(),
116 details: vec![],
117 }
118 }
119}
120
121impl fmt::Display for ServiceError {
122 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
123 write!(f, "[{}] {}", self.code, self.message)
124 }
125}
126
127impl std::error::Error for ServiceError {}
128
129pub trait IntoServiceError {
137 fn into_service_error(self) -> ServiceError;
139}
140
141impl IntoServiceError for ServiceError {
142 fn into_service_error(self) -> ServiceError {
143 self
144 }
145}
146
147impl From<std::io::Error> for ServiceError {
152 fn from(err: std::io::Error) -> Self {
153 Self::internal(format!("IO error: {}", err))
154 }
155}
156
157impl From<serde_json::Error> for ServiceError {
158 fn from(err: serde_json::Error) -> Self {
159 Self::invalid_request(format!("JSON error: {}", err))
160 }
161}
162
163impl From<anyhow::Error> for ServiceError {
164 fn from(err: anyhow::Error) -> Self {
165 Self::internal(err.to_string())
166 }
167}
168
169#[derive(Debug, thiserror::Error)]
175pub enum TransportError {
176 #[error("Interface not found: {0:?}")]
177 InterfaceNotFound(synapse_primitives::InterfaceId),
178
179 #[error("Method not found: {0:?}")]
180 MethodNotFound(synapse_primitives::MethodId),
181
182 #[error("IO error: {0}")]
183 Io(#[from] std::io::Error),
184
185 #[error("Codec error: {0}")]
186 Codec(String),
187
188 #[error("Timeout")]
189 Timeout,
190
191 #[error("Service unavailable")]
192 Unavailable,
193
194 #[error("Invalid request: {0}")]
195 InvalidRequest(String),
196
197 #[error("Other error: {0}")]
198 Other(String),
199}
200
201#[cfg(test)]
202mod tests {
203 use super::*;
204
205 #[test]
208 fn test_invalid_request() {
209 let err = ServiceError::invalid_request("bad input");
210 assert_eq!(err.status, RpcStatus::InvalidRequest);
211 assert_eq!(err.code, 400);
212 assert_eq!(err.message, "bad input");
213 }
214
215 #[test]
216 fn test_not_found() {
217 let err = ServiceError::not_found("user 42");
218 assert_eq!(err.status, RpcStatus::Error);
219 assert_eq!(err.code, 404);
220 assert_eq!(err.message, "user 42");
221 }
222
223 #[test]
224 fn test_unauthorized() {
225 let err = ServiceError::unauthorized("no token");
226 assert_eq!(err.status, RpcStatus::Error);
227 assert_eq!(err.code, 401);
228 }
229
230 #[test]
231 fn test_forbidden() {
232 let err = ServiceError::forbidden("not allowed");
233 assert_eq!(err.status, RpcStatus::Error);
234 assert_eq!(err.code, 403);
235 }
236
237 #[test]
238 fn test_internal() {
239 let err = ServiceError::internal("something broke");
240 assert_eq!(err.status, RpcStatus::Error);
241 assert_eq!(err.code, 500);
242 }
243
244 #[test]
245 fn test_unavailable() {
246 let err = ServiceError::unavailable("down for maintenance");
247 assert_eq!(err.status, RpcStatus::Unavailable);
248 assert_eq!(err.code, 503);
249 }
250
251 #[test]
252 fn test_timeout() {
253 let err = ServiceError::timeout("took too long");
254 assert_eq!(err.status, RpcStatus::Timeout);
255 assert_eq!(err.code, 504);
256 }
257
258 #[test]
259 fn test_custom() {
260 let err = ServiceError::custom(3001, "user banned");
261 assert_eq!(err.status, RpcStatus::Error);
262 assert_eq!(err.code, 3001);
263 assert_eq!(err.message, "user banned");
264 }
265
266 #[test]
269 fn test_to_proto() {
270 let err = ServiceError::new(RpcStatus::InvalidRequest, 400, "bad");
271 let proto = err.to_proto();
272 assert_eq!(proto.code, 400);
273 assert_eq!(proto.message, "bad");
274 assert!(proto.details.is_empty());
275 }
276
277 #[test]
280 fn test_display() {
281 let err = ServiceError::not_found("item 7");
282 let display = format!("{}", err);
283 assert_eq!(display, "[404] item 7");
284 }
285
286 #[test]
289 fn test_from_io_error() {
290 let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file missing");
291 let err: ServiceError = io_err.into();
292 assert_eq!(err.code, 500);
293 assert!(err.message.contains("IO error"));
294 }
295
296 #[test]
297 fn test_from_serde_json_error() {
298 let json_err = serde_json::from_str::<String>("not json{{{").unwrap_err();
299 let err: ServiceError = json_err.into();
300 assert_eq!(err.code, 400);
301 assert_eq!(err.status, RpcStatus::InvalidRequest);
302 assert!(err.message.contains("JSON error"));
303 }
304
305 #[test]
306 fn test_from_anyhow_error() {
307 let anyhow_err = anyhow::anyhow!("something went wrong");
308 let err: ServiceError = anyhow_err.into();
309 assert_eq!(err.code, 500);
310 assert!(err.message.contains("something went wrong"));
311 }
312
313 #[test]
316 fn test_into_service_error_identity() {
317 let original = ServiceError::custom(3001, "test");
318 let converted = original.clone().into_service_error();
319 assert_eq!(converted.code, 3001);
320 assert_eq!(converted.message, "test");
321 }
322
323 #[test]
326 fn test_clone() {
327 let err = ServiceError::internal("oops");
328 let cloned = err.clone();
329 assert_eq!(cloned.code, err.code);
330 assert_eq!(cloned.message, err.message);
331 }
332}