exomonad_core/effects/
error.rs1use serde::{Deserialize, Serialize};
9use serde_json::Value;
10
11#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
39#[serde(tag = "kind")]
40pub enum EffectError {
41 #[serde(rename = "not_found")]
43 NotFound {
44 resource: String,
46 },
47
48 #[serde(rename = "invalid_input")]
50 InvalidInput {
51 message: String,
53 },
54
55 #[serde(rename = "network_error")]
57 NetworkError {
58 message: String,
60 },
61
62 #[serde(rename = "permission_denied")]
64 PermissionDenied {
65 message: String,
67 },
68
69 #[serde(rename = "timeout")]
71 Timeout {
72 message: String,
74 },
75
76 #[serde(rename = "custom")]
81 Custom {
82 code: String,
84 message: String,
86 #[serde(skip_serializing_if = "Option::is_none")]
88 data: Option<Value>,
89 },
90}
91
92impl std::fmt::Display for EffectError {
93 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
94 match self {
95 EffectError::NotFound { resource } => write!(f, "Not found: {}", resource),
96 EffectError::InvalidInput { message } => write!(f, "Invalid input: {}", message),
97 EffectError::NetworkError { message } => write!(f, "Network error: {}", message),
98 EffectError::PermissionDenied { message } => {
99 write!(f, "Permission denied: {}", message)
100 }
101 EffectError::Timeout { message } => write!(f, "Timeout: {}", message),
102 EffectError::Custom { code, message, .. } => write!(f, "[{}] {}", code, message),
103 }
104 }
105}
106
107impl std::error::Error for EffectError {}
108
109impl EffectError {
110 pub fn not_found(resource: impl Into<String>) -> Self {
112 EffectError::NotFound {
113 resource: resource.into(),
114 }
115 }
116
117 pub fn invalid_input(message: impl Into<String>) -> Self {
119 EffectError::InvalidInput {
120 message: message.into(),
121 }
122 }
123
124 pub fn network_error(message: impl Into<String>) -> Self {
126 EffectError::NetworkError {
127 message: message.into(),
128 }
129 }
130
131 pub fn permission_denied(message: impl Into<String>) -> Self {
133 EffectError::PermissionDenied {
134 message: message.into(),
135 }
136 }
137
138 pub fn timeout(message: impl Into<String>) -> Self {
140 EffectError::Timeout {
141 message: message.into(),
142 }
143 }
144
145 pub fn custom(code: impl Into<String>, message: impl Into<String>) -> Self {
147 EffectError::Custom {
148 code: code.into(),
149 message: message.into(),
150 data: None,
151 }
152 }
153
154 pub fn custom_with_data(
156 code: impl Into<String>,
157 message: impl Into<String>,
158 data: Value,
159 ) -> Self {
160 EffectError::Custom {
161 code: code.into(),
162 message: message.into(),
163 data: Some(data),
164 }
165 }
166}
167
168#[cfg(test)]
169mod tests {
170 use super::*;
171
172 #[test]
173 fn test_error_serialization() {
174 let err = EffectError::NotFound {
175 resource: "issue/123".to_string(),
176 };
177 let json = serde_json::to_string(&err).unwrap();
178 assert!(json.contains("not_found"));
179 assert!(json.contains("issue/123"));
180 }
181
182 #[test]
183 fn test_custom_error_with_data() {
184 let err = EffectError::custom_with_data(
185 "egregore.signal_failed",
186 "Signal propagation failed",
187 serde_json::json!({"retry_count": 3}),
188 );
189 let json = serde_json::to_string(&err).unwrap();
190 assert!(json.contains("custom"));
191 assert!(json.contains("egregore.signal_failed"));
192 assert!(json.contains("retry_count"));
193 }
194
195 #[test]
196 fn test_error_display() {
197 let err = EffectError::not_found("file/README.md");
198 assert_eq!(err.to_string(), "Not found: file/README.md");
199 }
200}