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}