1use std::{num::TryFromIntError, path::PathBuf};
2
3#[derive(Debug, thiserror::Error)]
4pub enum Error {
5 #[error("{context}: {source}")]
6 Io {
7 context: String,
8 source: std::io::Error,
9 },
10
11 #[error("{0}")]
12 Syscall(#[from] nix::errno::Errno),
13
14 #[error("{0}")]
15 TryFromInt(#[from] TryFromIntError),
16
17 #[error("{path}: offset {offset} beyond file size {file_len}")]
18 OffsetBeyondFile {
19 path: PathBuf,
20 offset: u64,
21 file_len: u64,
22 },
23}
24
25impl Error {
26 pub fn io(context: impl Into<String>, source: std::io::Error) -> Self {
27 Self::Io {
28 context: context.into(),
29 source,
30 }
31 }
32}
33
34impl From<std::io::Error> for Error {
35 fn from(source: std::io::Error) -> Self {
36 Self::Io {
37 context: String::new(),
38 source,
39 }
40 }
41}
42
43pub type Result<T> = std::result::Result<T, Error>;
44
45#[cfg(test)]
46mod tests {
47 use super::*;
48
49 #[test]
50 fn test_io_error_display_with_context() {
51 let err = Error::io(
52 "/tmp/test.dat",
53 std::io::Error::new(std::io::ErrorKind::NotFound, "file not found"),
54 );
55 let msg = err.to_string();
56 assert!(msg.contains("/tmp/test.dat"), "msg: {msg}");
57 assert!(msg.contains("file not found"), "msg: {msg}");
58 }
59
60 #[test]
61 fn test_io_error_from_std() {
62 let io_err = std::io::Error::new(std::io::ErrorKind::PermissionDenied, "denied");
63 let err: Error = io_err.into();
64 assert!(matches!(err, Error::Io { .. }));
65 }
66
67 #[test]
68 fn test_syscall_error_from_errno() {
69 let err: Error = nix::errno::Errno::EBADF.into();
70 assert!(matches!(err, Error::Syscall(_)));
71 assert!(err.to_string().contains("EBADF"));
72 }
73
74 #[test]
75 fn test_offset_beyond_file_display() {
76 let err = Error::OffsetBeyondFile {
77 path: PathBuf::from("/data/big.bin"),
78 offset: 1000,
79 file_len: 500,
80 };
81 let msg = err.to_string();
82 assert!(msg.contains("/data/big.bin"), "msg: {msg}");
83 assert!(msg.contains("1000"), "msg: {msg}");
84 assert!(msg.contains("500"), "msg: {msg}");
85 }
86}