capnweb_core/
error.rs

1use serde::{Deserialize, Serialize};
2use serde_json::Value;
3use std::fmt;
4
5#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
6#[serde(rename_all = "snake_case")]
7pub enum ErrorCode {
8    BadRequest,
9    NotFound,
10    CapRevoked,
11    PermissionDenied,
12    Canceled,
13    Internal,
14}
15
16impl fmt::Display for ErrorCode {
17    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
18        let s = match self {
19            ErrorCode::BadRequest => "bad_request",
20            ErrorCode::NotFound => "not_found",
21            ErrorCode::CapRevoked => "cap_revoked",
22            ErrorCode::PermissionDenied => "permission_denied",
23            ErrorCode::Canceled => "canceled",
24            ErrorCode::Internal => "internal",
25        };
26        write!(f, "{}", s)
27    }
28}
29
30#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
31pub struct RpcError {
32    pub code: ErrorCode,
33    pub message: String,
34    #[serde(skip_serializing_if = "Option::is_none")]
35    pub data: Option<Value>,
36}
37
38impl RpcError {
39    pub fn new(code: ErrorCode, message: impl Into<String>) -> Self {
40        RpcError {
41            code,
42            message: message.into(),
43            data: None,
44        }
45    }
46
47    pub fn with_data(code: ErrorCode, message: impl Into<String>, data: Value) -> Self {
48        RpcError {
49            code,
50            message: message.into(),
51            data: Some(data),
52        }
53    }
54
55    pub fn bad_request(message: impl Into<String>) -> Self {
56        Self::new(ErrorCode::BadRequest, message)
57    }
58
59    pub fn not_found(message: impl Into<String>) -> Self {
60        Self::new(ErrorCode::NotFound, message)
61    }
62
63    pub fn cap_revoked(message: impl Into<String>) -> Self {
64        Self::new(ErrorCode::CapRevoked, message)
65    }
66
67    pub fn permission_denied(message: impl Into<String>) -> Self {
68        Self::new(ErrorCode::PermissionDenied, message)
69    }
70
71    pub fn canceled(message: impl Into<String>) -> Self {
72        Self::new(ErrorCode::Canceled, message)
73    }
74
75    pub fn internal(message: impl Into<String>) -> Self {
76        Self::new(ErrorCode::Internal, message)
77    }
78}
79
80impl fmt::Display for RpcError {
81    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
82        write!(f, "{:?}: {}", self.code, self.message)
83    }
84}
85
86impl std::error::Error for RpcError {}
87
88impl From<serde_json::Error> for RpcError {
89    fn from(err: serde_json::Error) -> Self {
90        RpcError::bad_request(format!("JSON error: {}", err))
91    }
92}
93
94impl From<std::io::Error> for RpcError {
95    fn from(err: std::io::Error) -> Self {
96        RpcError::internal(format!("IO error: {}", err))
97    }
98}
99
100#[cfg(test)]
101mod tests {
102    use super::*;
103
104    #[test]
105    fn test_error_creation() {
106        let err = RpcError::new(ErrorCode::BadRequest, "Invalid input");
107        assert_eq!(err.code, ErrorCode::BadRequest);
108        assert_eq!(err.message, "Invalid input");
109        assert_eq!(err.data, None);
110    }
111
112    #[test]
113    fn test_error_with_data() {
114        let data = serde_json::json!({"field": "value"});
115        let err = RpcError::with_data(ErrorCode::Internal, "Server error", data.clone());
116        assert_eq!(err.code, ErrorCode::Internal);
117        assert_eq!(err.message, "Server error");
118        assert_eq!(err.data, Some(data));
119    }
120
121    #[test]
122    fn test_convenience_constructors() {
123        let err = RpcError::bad_request("Bad input");
124        assert_eq!(err.code, ErrorCode::BadRequest);
125
126        let err = RpcError::not_found("Resource not found");
127        assert_eq!(err.code, ErrorCode::NotFound);
128
129        let err = RpcError::cap_revoked("Capability revoked");
130        assert_eq!(err.code, ErrorCode::CapRevoked);
131
132        let err = RpcError::permission_denied("Access denied");
133        assert_eq!(err.code, ErrorCode::PermissionDenied);
134
135        let err = RpcError::canceled("Operation canceled");
136        assert_eq!(err.code, ErrorCode::Canceled);
137
138        let err = RpcError::internal("Internal error");
139        assert_eq!(err.code, ErrorCode::Internal);
140    }
141
142    #[test]
143    fn test_error_serialization() {
144        let err = RpcError::new(ErrorCode::NotFound, "Resource not found");
145        let json = serde_json::to_string(&err).unwrap();
146        let deserialized: RpcError = serde_json::from_str(&json).unwrap();
147        assert_eq!(err, deserialized);
148    }
149
150    #[test]
151    fn test_error_serialization_with_data() {
152        let data = serde_json::json!({"id": 123});
153        let err = RpcError::with_data(ErrorCode::BadRequest, "Invalid ID", data);
154        let json = serde_json::to_string(&err).unwrap();
155        assert!(json.contains("\"data\""));
156        let deserialized: RpcError = serde_json::from_str(&json).unwrap();
157        assert_eq!(err, deserialized);
158    }
159
160    #[test]
161    fn test_error_display() {
162        let err = RpcError::internal("Something went wrong");
163        let display = format!("{}", err);
164        assert!(display.contains("Internal"));
165        assert!(display.contains("Something went wrong"));
166    }
167}