Skip to main content

pagers_core/
error.rs

1use std::{num::TryFromIntError, path::PathBuf};
2
3const MLOCK_HINT: &str = "\
4\n\nPossible fixes:\
5\n  ulimit -l unlimited\
6\n  setcap cap_ipc_lock+ep <binary>\
7\n  # Kubernetes:\
8\n  securityContext:\
9\n    capabilities:\
10\n      add: [\"IPC_LOCK\"]";
11
12#[derive(Debug, thiserror::Error)]
13pub enum MlockError {
14    #[error("{call} failed: permission denied (EPERM){}", MLOCK_HINT)]
15    PermissionDenied { call: &'static str },
16
17    #[error(
18        "{call} failed: cannot lock {len} bytes — RLIMIT_MEMLOCK too low (ENOMEM){}",
19        MLOCK_HINT
20    )]
21    OutOfMemory { call: &'static str, len: usize },
22
23    #[error("{call}: {source}")]
24    Other {
25        call: &'static str,
26        source: std::io::Error,
27    },
28}
29
30#[derive(Debug, thiserror::Error)]
31pub enum Error {
32    #[error("{context}: {source}")]
33    Io {
34        context: String,
35        source: std::io::Error,
36    },
37
38    #[error(transparent)]
39    Mlock(#[from] MlockError),
40
41    #[error("{0}")]
42    Syscall(#[from] nix::errno::Errno),
43
44    #[error("{0}")]
45    TryFromInt(#[from] TryFromIntError),
46
47    #[cfg(feature = "rayon")]
48    #[error("{0}")]
49    ThreadPool(#[from] rayon::ThreadPoolBuildError),
50
51    #[error("{path}: offset {offset} beyond file size {file_len}")]
52    OffsetBeyondFile {
53        path: PathBuf,
54        offset: u64,
55        file_len: u64,
56    },
57}
58
59impl Error {
60    pub fn io(context: impl Into<String>, source: std::io::Error) -> Self {
61        Self::Io {
62            context: context.into(),
63            source,
64        }
65    }
66}
67
68impl From<std::io::Error> for Error {
69    fn from(source: std::io::Error) -> Self {
70        Self::Io {
71            context: String::new(),
72            source,
73        }
74    }
75}
76
77pub type Result<T> = std::result::Result<T, Error>;
78
79#[cfg(test)]
80mod tests {
81    use super::*;
82
83    #[test]
84    fn test_io_error_display_with_context() {
85        let err = Error::io(
86            "/tmp/test.dat",
87            std::io::Error::new(std::io::ErrorKind::NotFound, "file not found"),
88        );
89        let msg = err.to_string();
90        assert!(msg.contains("/tmp/test.dat"), "msg: {msg}");
91        assert!(msg.contains("file not found"), "msg: {msg}");
92    }
93
94    #[test]
95    fn test_io_error_from_std() {
96        let io_err = std::io::Error::new(std::io::ErrorKind::PermissionDenied, "denied");
97        let err: Error = io_err.into();
98        assert!(matches!(err, Error::Io { .. }));
99    }
100
101    #[test]
102    fn test_syscall_error_from_errno() {
103        let err: Error = nix::errno::Errno::EBADF.into();
104        assert!(matches!(err, Error::Syscall(_)));
105        assert!(err.to_string().contains("EBADF"));
106    }
107
108    #[test]
109    fn test_offset_beyond_file_display() {
110        let err = Error::OffsetBeyondFile {
111            path: PathBuf::from("/data/big.bin"),
112            offset: 1000,
113            file_len: 500,
114        };
115        let msg = err.to_string();
116        assert!(msg.contains("/data/big.bin"), "msg: {msg}");
117        assert!(msg.contains("1000"), "msg: {msg}");
118        assert!(msg.contains("500"), "msg: {msg}");
119    }
120}