1use thiserror::Error;
2
3#[derive(Error, Debug)]
5pub enum BoxError {
6 #[error("VM boot failed: {message}{}", .hint.as_ref().map(|h| format!(" (hint: {h})")).unwrap_or_default())]
8 BoxBootError {
9 message: String,
10 hint: Option<String>,
11 },
12
13 #[error("Timeout: {0}")]
15 TimeoutError(String),
16
17 #[error("I/O error: {0}")]
19 IoError(#[from] std::io::Error),
20
21 #[error("Serialization error: {0}")]
23 SerializationError(String),
24
25 #[error("Configuration error: {0}")]
27 ConfigError(String),
28
29 #[error("TEE configuration error: {0}")]
31 TeeConfig(String),
32
33 #[error("TEE hardware not available: {0}")]
35 TeeNotSupported(String),
36
37 #[error("Attestation error: {0}")]
39 AttestationError(String),
40
41 #[error("OCI image error: {0}")]
43 OciImageError(String),
44
45 #[error("Registry error: {registry} - {message}")]
47 RegistryError { registry: String, message: String },
48
49 #[error("Cache error: {0}")]
51 CacheError(String),
52
53 #[error("Pool error: {0}")]
55 PoolError(String),
56
57 #[error("Exec error: {0}")]
59 ExecError(String),
60
61 #[error("Build error: {0}")]
63 BuildError(String),
64
65 #[error("Network error: {0}")]
67 NetworkError(String),
68
69 #[error("VM state error: {0}")]
71 StateError(String),
72
73 #[error("Audit error: {0}")]
75 AuditError(String),
76
77 #[error("Resize error: {0}")]
79 ResizeError(String),
80
81 #[error("{0}")]
83 Other(String),
84}
85
86impl From<serde_json::Error> for BoxError {
87 fn from(err: serde_json::Error) -> Self {
88 BoxError::SerializationError(err.to_string())
89 }
90}
91
92impl From<serde_yaml::Error> for BoxError {
93 fn from(err: serde_yaml::Error) -> Self {
94 BoxError::SerializationError(err.to_string())
95 }
96}
97
98pub type Result<T> = std::result::Result<T, BoxError>;
100
101#[cfg(test)]
102mod tests {
103 use super::*;
104
105 #[test]
106 fn test_box_boot_error_display() {
107 let error = BoxError::BoxBootError {
108 message: "Failed to start VM".to_string(),
109 hint: Some("Check virtualization support".to_string()),
110 };
111 assert_eq!(
115 error.to_string(),
116 "VM boot failed: Failed to start VM (hint: Check virtualization support)"
117 );
118 }
119
120 #[test]
121 fn test_box_boot_error_without_hint() {
122 let error = BoxError::BoxBootError {
123 message: "No kernel found".to_string(),
124 hint: None,
125 };
126 assert_eq!(error.to_string(), "VM boot failed: No kernel found");
127 }
128
129 #[test]
130 fn test_timeout_error_display() {
131 let error = BoxError::TimeoutError("Operation timed out after 30s".to_string());
132 assert_eq!(error.to_string(), "Timeout: Operation timed out after 30s");
133 }
134
135 #[test]
136 fn test_io_error_conversion() {
137 let io_error = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
138 let box_error: BoxError = io_error.into();
139 assert!(matches!(box_error, BoxError::IoError(_)));
140 assert!(box_error.to_string().contains("file not found"));
141 }
142
143 #[test]
144 fn test_serialization_error_display() {
145 let error = BoxError::SerializationError("Invalid JSON".to_string());
146 assert_eq!(error.to_string(), "Serialization error: Invalid JSON");
147 }
148
149 #[test]
150 fn test_config_error_display() {
151 let error = BoxError::ConfigError("Missing required field".to_string());
152 assert_eq!(
153 error.to_string(),
154 "Configuration error: Missing required field"
155 );
156 }
157
158 #[test]
159 fn test_other_error_display() {
160 let error = BoxError::Other("Unknown error occurred".to_string());
161 assert_eq!(error.to_string(), "Unknown error occurred");
162 }
163
164 #[test]
165 fn test_tee_config_error_display() {
166 let error = BoxError::TeeConfig("Failed to set TEE config file".to_string());
167 assert_eq!(
168 error.to_string(),
169 "TEE configuration error: Failed to set TEE config file"
170 );
171 }
172
173 #[test]
174 fn test_tee_not_supported_error_display() {
175 let error = BoxError::TeeNotSupported("AMD SEV-SNP not available".to_string());
176 assert_eq!(
177 error.to_string(),
178 "TEE hardware not available: AMD SEV-SNP not available"
179 );
180 }
181
182 #[test]
183 fn test_attestation_error_display() {
184 let error = BoxError::AttestationError("Failed to get SNP report".to_string());
185 assert_eq!(
186 error.to_string(),
187 "Attestation error: Failed to get SNP report"
188 );
189 }
190
191 #[test]
192 fn test_oci_image_error_display() {
193 let error = BoxError::OciImageError("Invalid manifest".to_string());
194 assert_eq!(error.to_string(), "OCI image error: Invalid manifest");
195 }
196
197 #[test]
198 fn test_registry_error_display() {
199 let error = BoxError::RegistryError {
200 registry: "ghcr.io".to_string(),
201 message: "Authentication failed".to_string(),
202 };
203 assert_eq!(
204 error.to_string(),
205 "Registry error: ghcr.io - Authentication failed"
206 );
207 }
208
209 #[test]
210 fn test_serde_json_error_conversion() {
211 let json_str = "{ invalid json }";
212 let result: std::result::Result<serde_json::Value, _> = serde_json::from_str(json_str);
213 let json_error = result.unwrap_err();
214 let box_error: BoxError = json_error.into();
215 assert!(matches!(box_error, BoxError::SerializationError(_)));
216 }
217
218 #[test]
219 fn test_serde_yaml_error_conversion() {
220 let yaml_str = "invalid: yaml: content:";
221 let result: std::result::Result<serde_yaml::Value, _> = serde_yaml::from_str(yaml_str);
222 let yaml_error = result.unwrap_err();
223 let box_error: BoxError = yaml_error.into();
224 assert!(matches!(box_error, BoxError::SerializationError(_)));
225 }
226
227 #[test]
228 fn test_result_type_alias() {
229 fn returns_ok() -> Result<i32> {
230 Ok(42)
231 }
232
233 fn returns_err() -> Result<i32> {
234 Err(BoxError::Other("test error".to_string()))
235 }
236
237 assert_eq!(returns_ok().unwrap(), 42);
238 assert!(returns_err().is_err());
239 }
240
241 #[test]
242 fn test_resize_error_display() {
243 let error = BoxError::ResizeError("Cannot change vCPU count".to_string());
244 assert_eq!(error.to_string(), "Resize error: Cannot change vCPU count");
245 }
246
247 #[test]
248 fn test_error_is_debug() {
249 let error = BoxError::Other("test".to_string());
250 let debug_str = format!("{:?}", error);
251 assert!(debug_str.contains("Other"));
252 }
253
254 #[test]
255 fn test_cache_error_display() {
256 let error = BoxError::CacheError("Rootfs cache corrupted".to_string());
257 assert_eq!(error.to_string(), "Cache error: Rootfs cache corrupted");
258 }
259
260 #[test]
261 fn test_pool_error_display() {
262 let error = BoxError::PoolError("No idle VMs available".to_string());
263 assert_eq!(error.to_string(), "Pool error: No idle VMs available");
264 }
265
266 #[test]
267 fn test_exec_error_display() {
268 let error = BoxError::ExecError("Command not found".to_string());
269 assert_eq!(error.to_string(), "Exec error: Command not found");
270 }
271
272 #[test]
273 fn test_build_error_display() {
274 let error = BoxError::BuildError("Dockerfile parse failed".to_string());
275 assert_eq!(error.to_string(), "Build error: Dockerfile parse failed");
276 }
277}