1use std::io;
10use std::path::{Path, PathBuf};
11
12#[derive(Debug, thiserror::Error)]
14#[non_exhaustive]
15pub enum CoreError {
16 #[error("i/o error at {path}: {source}")]
18 Io {
19 path: PathBuf,
21 source: io::Error,
23 },
24
25 #[error("i/o error: {0}")]
27 BareIo(#[from] io::Error),
28
29 #[error("bad magic: expected {expected:#010x}, found {found:#010x}")]
32 BadMagic {
33 expected: u32,
35 found: u32,
37 },
38
39 #[error("unsupported format version {found} (this build supports {supported})")]
41 UnsupportedVersion {
42 found: u16,
44 supported: u16,
46 },
47
48 #[error("page {page_id} failed crc check (header {expected:#010x}, computed {computed:#010x})")]
50 PageCorrupt {
51 page_id: u64,
53 expected: u32,
55 computed: u32,
57 },
58
59 #[error("malformed page: {0}")]
62 MalformedPage(String),
63
64 #[error("serialization error: {0}")]
66 Serialization(#[from] postcard::Error),
67
68 #[error("value too large: {0}")]
71 TooLarge(String),
72
73 #[error("not found: {0}")]
75 NotFound(String),
76
77 #[error("already exists: {0}")]
79 AlreadyExists(String),
80
81 #[error("invalid argument: {0}")]
83 InvalidArgument(String),
84}
85
86impl CoreError {
87 #[must_use]
89 pub fn io(path: impl AsRef<Path>, source: io::Error) -> Self {
90 Self::Io {
91 path: path.as_ref().to_path_buf(),
92 source,
93 }
94 }
95}
96
97pub type Result<T> = std::result::Result<T, CoreError>;
99
100#[cfg(test)]
101mod tests {
102 use super::*;
103 use std::io::ErrorKind;
104
105 #[test]
110 fn every_variant_formats_a_useful_message() {
111 let io = CoreError::io(
112 "/tmp/x",
113 io::Error::new(ErrorKind::PermissionDenied, "boom"),
114 );
115 assert_eq!(io.to_string(), "i/o error at /tmp/x: boom");
116
117 let bare: CoreError = io::Error::new(ErrorKind::UnexpectedEof, "eof").into();
118 assert_eq!(bare.to_string(), "i/o error: eof");
119
120 assert_eq!(
121 CoreError::BadMagic {
122 expected: 0xDEAD_BEEF,
123 found: 0x0000_0001
124 }
125 .to_string(),
126 "bad magic: expected 0xdeadbeef, found 0x00000001",
127 );
128 assert_eq!(
129 CoreError::UnsupportedVersion {
130 found: 9,
131 supported: 2
132 }
133 .to_string(),
134 "unsupported format version 9 (this build supports 2)",
135 );
136 assert_eq!(
137 CoreError::PageCorrupt {
138 page_id: 7,
139 expected: 0x0000_00ff,
140 computed: 0x0000_0100
141 }
142 .to_string(),
143 "page 7 failed crc check (header 0x000000ff, computed 0x00000100)",
144 );
145
146 assert_eq!(
147 CoreError::MalformedPage("len".into()).to_string(),
148 "malformed page: len"
149 );
150 assert_eq!(
151 CoreError::TooLarge("payload".into()).to_string(),
152 "value too large: payload"
153 );
154 assert_eq!(CoreError::NotFound("c".into()).to_string(), "not found: c");
155 assert_eq!(
156 CoreError::AlreadyExists("c".into()).to_string(),
157 "already exists: c"
158 );
159 assert_eq!(
160 CoreError::InvalidArgument("dim".into()).to_string(),
161 "invalid argument: dim",
162 );
163
164 let de = postcard::from_bytes::<u32>(&[]).unwrap_err();
166 let err: CoreError = de.into();
167 assert!(err.to_string().starts_with("serialization error:"), "{err}");
168 }
169
170 #[test]
171 fn io_constructor_tags_the_path() {
172 let err = CoreError::io("data/seg.000", io::Error::from(ErrorKind::NotFound));
173 match err {
174 CoreError::Io { path, .. } => assert_eq!(path, PathBuf::from("data/seg.000")),
175 other => panic!("expected Io, got {other:?}"),
176 }
177 }
178}