1use crate::format::Format;
4use crate::path::{Path, PathError};
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8#[non_exhaustive]
9pub enum CodecErrorKind {
10 UnsupportedProfile,
11 UnsupportedVersion,
12 Syntax,
13 InvalidUnicode,
14 InvalidNode,
15 InvalidBase64,
16 DuplicateKey,
17 OutOfRange,
18 UnsupportedValue,
19 TypeMismatch,
20 AmbiguousOption,
21 Noncanonical,
22 ResourceLimit,
23 Io,
24}
25
26impl CodecErrorKind {
27 pub const fn as_str(self) -> &'static str {
29 match self {
30 Self::UnsupportedProfile => "unsupported_profile",
31 Self::UnsupportedVersion => "unsupported_version",
32 Self::Syntax => "syntax",
33 Self::InvalidUnicode => "invalid_unicode",
34 Self::InvalidNode => "invalid_node",
35 Self::InvalidBase64 => "invalid_base64",
36 Self::DuplicateKey => "duplicate_key",
37 Self::OutOfRange => "out_of_range",
38 Self::UnsupportedValue => "unsupported_value",
39 Self::TypeMismatch => "type_mismatch",
40 Self::AmbiguousOption => "ambiguous_option",
41 Self::Noncanonical => "noncanonical",
42 Self::ResourceLimit => "resource_limit",
43 Self::Io => "io",
44 }
45 }
46}
47
48#[derive(Debug, Clone, Copy, PartialEq, Eq)]
50pub enum CodecOperation {
51 Encode,
52 Decode,
53}
54
55impl std::fmt::Display for CodecOperation {
56 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
57 match self {
58 CodecOperation::Encode => write!(f, "encode"),
59 CodecOperation::Decode => write!(f, "decode"),
60 }
61 }
62}
63
64#[derive(Debug)]
74#[non_exhaustive]
75pub enum Error {
76 Path(PathError),
78
79 NoRoute { path: Path },
81
82 Codec {
84 kind: CodecErrorKind,
85 operation: CodecOperation,
86 format: Format,
87 message: String,
88 },
89
90 UnsupportedFormat(Format),
92
93 Ll(structfs_ll_store::LLError),
95
96 Io(std::io::Error),
98
99 Store {
101 store: &'static str,
102 operation: &'static str,
103 message: String,
104 },
105
106 NotFound { path: Path },
112
113 PermissionDenied { message: String },
115
116 Conflict { message: String },
119
120 Overloaded { message: String },
122
123 DeadlineExceeded { message: String },
125
126 ResourceLimit { message: String },
128
129 Cancelled { message: String },
132}
133
134impl Error {
135 pub fn store(store: &'static str, operation: &'static str, message: impl Into<String>) -> Self {
137 Error::Store {
138 store,
139 operation,
140 message: message.into(),
141 }
142 }
143
144 pub fn decode(format: Format, message: impl Into<String>) -> Self {
146 Error::Codec {
147 kind: CodecErrorKind::Syntax,
148 operation: CodecOperation::Decode,
149 format,
150 message: message.into(),
151 }
152 }
153
154 pub fn encode(format: Format, message: impl Into<String>) -> Self {
156 Error::Codec {
157 kind: CodecErrorKind::UnsupportedValue,
158 operation: CodecOperation::Encode,
159 format,
160 message: message.into(),
161 }
162 }
163
164 pub fn not_found(path: Path) -> Self {
166 Error::NotFound { path }
167 }
168
169 pub fn permission_denied(message: impl Into<String>) -> Self {
171 Error::PermissionDenied {
172 message: message.into(),
173 }
174 }
175
176 pub fn conflict(message: impl Into<String>) -> Self {
178 Error::Conflict {
179 message: message.into(),
180 }
181 }
182
183 pub fn overloaded(message: impl Into<String>) -> Self {
185 Error::Overloaded {
186 message: message.into(),
187 }
188 }
189
190 pub fn deadline_exceeded(message: impl Into<String>) -> Self {
192 Error::DeadlineExceeded {
193 message: message.into(),
194 }
195 }
196
197 pub fn resource_limit(message: impl Into<String>) -> Self {
199 Error::ResourceLimit {
200 message: message.into(),
201 }
202 }
203
204 pub fn cancelled(message: impl Into<String>) -> Self {
206 Error::Cancelled {
207 message: message.into(),
208 }
209 }
210
211 pub fn is_cancelled(&self) -> bool {
213 matches!(self, Error::Cancelled { .. })
214 }
215
216 pub fn is_not_found(&self) -> bool {
218 matches!(self, Error::NotFound { .. })
219 }
220}
221
222impl std::fmt::Display for Error {
223 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
224 match self {
225 Error::Path(e) => write!(f, "path error: {}", e),
226 Error::NoRoute { path } => write!(f, "no route to {}", path),
227 Error::Codec {
228 operation,
229 format,
230 message,
231 ..
232 } => {
233 write!(f, "{} failed for format {}: {}", operation, format, message)
234 }
235 Error::UnsupportedFormat(format) => {
236 write!(f, "unsupported format: {}", format)
237 }
238 Error::Ll(e) => write!(f, "low-level error: {}", e),
239 Error::Io(e) => write!(f, "I/O error: {}", e),
240 Error::Store {
241 store,
242 operation,
243 message,
244 } => write!(f, "{}::{}: {}", store, operation, message),
245 Error::NotFound { path } => write!(f, "not found: {}", path),
246 Error::PermissionDenied { message } => write!(f, "permission denied: {}", message),
247 Error::Conflict { message } => write!(f, "conflict: {}", message),
248 Error::Overloaded { message } => write!(f, "overloaded: {}", message),
249 Error::DeadlineExceeded { message } => write!(f, "deadline exceeded: {}", message),
250 Error::ResourceLimit { message } => write!(f, "resource limit: {}", message),
251 Error::Cancelled { message } => write!(f, "cancelled: {}", message),
252 }
253 }
254}
255
256impl std::error::Error for Error {
257 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
258 match self {
259 Error::Path(e) => Some(e),
260 Error::Ll(e) => Some(e),
261 Error::Io(e) => Some(e),
262 _ => None,
263 }
264 }
265}
266
267impl From<PathError> for Error {
268 fn from(e: PathError) -> Self {
269 Error::Path(e)
270 }
271}
272
273impl From<structfs_ll_store::LLError> for Error {
274 fn from(e: structfs_ll_store::LLError) -> Self {
275 Error::Ll(e)
276 }
277}
278
279impl From<std::io::Error> for Error {
280 fn from(e: std::io::Error) -> Self {
281 Error::Io(e)
282 }
283}
284
285#[cfg(test)]
286mod tests {
287 use super::*;
288 use std::error::Error as StdError;
289
290 #[test]
291 fn error_display() {
292 let e = Error::NoRoute {
293 path: Path::parse("foo/bar").unwrap(),
294 };
295 assert_eq!(e.to_string(), "no route to foo/bar");
296
297 let e = Error::UnsupportedFormat(Format::PROTOBUF);
298 assert!(format!("{}", e).contains("protobuf"));
299 }
300
301 #[test]
302 fn path_error_display() {
303 let e = Error::Path(PathError::InvalidComponent {
304 component: "bad".to_string(),
305 position: 1,
306 message: "invalid".to_string(),
307 });
308 assert!(format!("{}", e).contains("path error"));
309 }
310
311 #[test]
312 fn codec_decode_error_display() {
313 let e = Error::decode(Format::JSON, "unexpected token");
314 let display = format!("{}", e);
315 assert!(display.contains("decode"));
316 assert!(display.contains("json"));
317 assert!(display.contains("unexpected token"));
318 }
319
320 #[test]
321 fn codec_encode_error_display() {
322 let e = Error::encode(Format::CBOR, "serialization failed");
323 let display = format!("{}", e);
324 assert!(display.contains("encode"));
325 assert!(display.contains("cbor"));
326 assert!(display.contains("serialization failed"));
327 }
328
329 #[test]
330 fn ll_error_display() {
331 let ll_err = structfs_ll_store::LLError::NotSupported;
332 let e = Error::Ll(ll_err);
333 let display = format!("{}", e);
334 assert!(display.contains("low-level error"));
335 }
336
337 #[test]
338 fn store_error_display() {
339 let e = Error::store("http_broker", "read", "Request 42 not found");
340 assert_eq!(e.to_string(), "http_broker::read: Request 42 not found");
341 }
342
343 #[test]
344 fn io_error_display() {
345 let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
346 let e = Error::Io(io_err);
347 let display = format!("{}", e);
348 assert!(display.contains("I/O error"));
349 assert!(display.contains("file not found"));
350 }
351
352 #[test]
353 fn path_error_source() {
354 let e = Error::Path(PathError::InvalidPath {
355 message: "test".to_string(),
356 });
357 assert!(StdError::source(&e).is_some());
358 }
359
360 #[test]
361 fn ll_error_source() {
362 let ll_err = structfs_ll_store::LLError::NotSupported;
363 let e = Error::Ll(ll_err);
364 assert!(StdError::source(&e).is_some());
365 }
366
367 #[test]
368 fn io_error_source() {
369 let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
370 let e = Error::Io(io_err);
371 assert!(StdError::source(&e).is_some());
372 }
373
374 #[test]
375 fn store_error_source_is_none() {
376 let e = Error::store("test", "op", "message");
377 assert!(StdError::source(&e).is_none());
378 }
379
380 #[test]
381 fn path_error_conversion() {
382 let path_err = PathError::InvalidPath {
383 message: "test".to_string(),
384 };
385 let e: Error = path_err.into();
386 assert!(matches!(e, Error::Path(_)));
387 }
388
389 #[test]
390 fn ll_error_conversion() {
391 let ll_err = structfs_ll_store::LLError::ResourceExhausted;
392 let e: Error = ll_err.into();
393 assert!(matches!(e, Error::Ll(_)));
394 }
395
396 #[test]
397 fn io_error_conversion() {
398 let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
399 let e: Error = io_err.into();
400 assert!(matches!(e, Error::Io(_)));
401 }
402
403 #[test]
404 fn typed_variant_display() {
405 let e = Error::not_found(Path::parse("users/123").unwrap());
406 assert_eq!(e.to_string(), "not found: users/123");
407 assert!(e.is_not_found());
408
409 assert_eq!(
410 Error::permission_denied("read-only mount").to_string(),
411 "permission denied: read-only mount"
412 );
413 assert_eq!(
414 Error::conflict("stale version").to_string(),
415 "conflict: stale version"
416 );
417 assert_eq!(
418 Error::overloaded("queue full").to_string(),
419 "overloaded: queue full"
420 );
421 assert_eq!(
422 Error::deadline_exceeded("10s elapsed").to_string(),
423 "deadline exceeded: 10s elapsed"
424 );
425 assert_eq!(
426 Error::resource_limit("frame too large").to_string(),
427 "resource limit: frame too large"
428 );
429 }
430
431 #[test]
432 fn typed_variants_have_no_source() {
433 assert!(StdError::source(&Error::conflict("x")).is_none());
434 assert!(!Error::conflict("x").is_not_found());
435 }
436
437 #[test]
438 fn codec_operation_display() {
439 assert_eq!(CodecOperation::Encode.to_string(), "encode");
440 assert_eq!(CodecOperation::Decode.to_string(), "decode");
441 }
442
443 #[test]
444 fn codec_error_with_operation() {
445 let e = Error::Codec {
446 kind: CodecErrorKind::Syntax,
447 operation: CodecOperation::Decode,
448 format: Format::JSON,
449 message: "test".to_string(),
450 };
451 assert!(matches!(
452 e,
453 Error::Codec {
454 operation: CodecOperation::Decode,
455 ..
456 }
457 ));
458 }
459}