Skip to main content

pager_lang/
error.rs

1//! The error a memory operation reports when it cannot complete.
2
3use core::fmt;
4
5/// The reason a [`Region`](crate::Region) operation failed.
6///
7/// The variants separate the three places things go wrong: a request that does not describe
8/// a valid region ([`ZeroSize`](Self::ZeroSize), [`SizeOverflow`](Self::SizeOverflow)), the
9/// operating system refusing a call ([`Map`](Self::Map), [`Protect`](Self::Protect)), and a
10/// write that does not fit the region as it currently stands ([`NotWritable`](Self::NotWritable),
11/// [`OutOfBounds`](Self::OutOfBounds)). The OS-level variants carry the raw error code —
12/// `errno` on Unix, `GetLastError` on Windows — so the underlying cause is not lost.
13///
14/// The set is `#[non_exhaustive]`: future platforms or features may add variants, so a
15/// `match` on this type must include a wildcard arm.
16///
17/// # Examples
18///
19/// ```
20/// use pager_lang::{PagerError, Region};
21///
22/// // A region must be at least one page; zero bytes is rejected up front.
23/// assert!(matches!(Region::new(0), Err(PagerError::ZeroSize)));
24/// ```
25#[derive(Clone, PartialEq, Eq, Debug)]
26#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
27#[non_exhaustive]
28pub enum PagerError {
29    /// A region of zero bytes was requested. A region must span at least one page; ask for
30    /// the number of bytes you need and the request is rounded up to whole pages.
31    ZeroSize,
32    /// Rounding the request up to whole pages, or adding the guard pages, overflowed
33    /// `usize`. The requested size is too large to represent, let alone map.
34    SizeOverflow,
35    /// The operating system refused to map the region. The wrapped value is the OS error
36    /// code (`errno` / `GetLastError`); the usual cause is exhausted address space or
37    /// memory.
38    Map(i32),
39    /// The operating system refused to change the region's protection. The wrapped value is
40    /// the OS error code. A frequent cause is a platform that forbids writable-and-executable
41    /// pages, which rejects [`Protection::ReadWriteExecute`](crate::Protection::ReadWriteExecute).
42    Protect(i32),
43    /// A write was attempted on a region whose current protection does not permit writing.
44    /// Flip it to [`Protection::ReadWrite`](crate::Protection::ReadWrite) with
45    /// [`Region::protect`](crate::Region::protect) first.
46    NotWritable,
47    /// A write would have fallen outside the region: `offset + len` exceeded the region's
48    /// length. The fields report what was attempted against the region that exists.
49    OutOfBounds {
50        /// The offset, in bytes, the write started at.
51        offset: usize,
52        /// The number of bytes the write would have copied.
53        len: usize,
54        /// The length of the region, in bytes.
55        region_len: usize,
56    },
57}
58
59impl fmt::Display for PagerError {
60    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
61        match self {
62            PagerError::ZeroSize => f.write_str("cannot map a region of zero bytes"),
63            PagerError::SizeOverflow => {
64                f.write_str("region size overflowed usize when rounded to whole pages")
65            }
66            PagerError::Map(code) => {
67                write!(
68                    f,
69                    "the operating system could not map the region (os error {code})"
70                )
71            }
72            PagerError::Protect(code) => write!(
73                f,
74                "the operating system could not change the region's protection (os error {code})"
75            ),
76            PagerError::NotWritable => {
77                f.write_str("the region is not writable under its current protection")
78            }
79            PagerError::OutOfBounds {
80                offset,
81                len,
82                region_len,
83            } => write!(
84                f,
85                "a write of {len} byte(s) at offset {offset} exceeds the {region_len}-byte region"
86            ),
87        }
88    }
89}
90
91impl core::error::Error for PagerError {}
92
93#[cfg(test)]
94#[allow(
95    clippy::unwrap_used,
96    clippy::expect_used,
97    clippy::panic,
98    reason = "tests assert on specific outcomes; a wrong outcome should fail the test loudly"
99)]
100mod tests {
101    use super::PagerError;
102
103    #[test]
104    fn test_display_mentions_the_failure() {
105        assert!(PagerError::ZeroSize.to_string().contains("zero"));
106        assert!(PagerError::SizeOverflow.to_string().contains("overflow"));
107        assert!(PagerError::Map(12).to_string().contains("os error 12"));
108        assert!(PagerError::Protect(13).to_string().contains("os error 13"));
109        assert!(PagerError::NotWritable.to_string().contains("not writable"));
110        let oob = PagerError::OutOfBounds {
111            offset: 8,
112            len: 16,
113            region_len: 16,
114        };
115        let text = oob.to_string();
116        assert!(text.contains('8') && text.contains("16"));
117    }
118
119    #[test]
120    fn test_is_a_std_error() {
121        fn assert_error<E: core::error::Error>(_: &E) {}
122        assert_error(&PagerError::ZeroSize);
123    }
124}