Skip to main content

bedrock_leveldb/
error.rs

1use std::path::{Path, PathBuf};
2use thiserror::Error;
3
4/// Crate-wide result type.
5pub type Result<T> = std::result::Result<T, LevelDbError>;
6
7/// Stable category for a [`LevelDbError`].
8///
9/// Prefer matching this enum or [`LevelDbError::path`] in application code
10/// instead of parsing the human-readable [`std::fmt::Display`] output.
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12#[non_exhaustive]
13pub enum ErrorKind {
14    /// Filesystem or operating-system I/O failed.
15    Io,
16    /// On-disk bytes were malformed, truncated, or failed validation.
17    Corruption,
18    /// Caller-supplied options or inputs were invalid.
19    InvalidArgument,
20    /// A requested codec or behavior is disabled or not implemented.
21    Unsupported,
22    /// Compression or decompression failed.
23    Compression,
24    /// A scan observed a caller-supplied cancellation flag.
25    Cancelled,
26    /// A mutating operation was requested from a read-only handle.
27    ReadOnly,
28    /// Opening failed because the target already existed.
29    AlreadyExists,
30    /// A database directory or required metadata file was missing.
31    NotFound,
32    /// An internal synchronization primitive was poisoned.
33    LockPoisoned,
34    /// The optional async wrapper failed to join a blocking task.
35    Join,
36}
37
38/// Errors returned while opening, reading, writing, or repairing a database.
39#[derive(Debug, Error)]
40#[non_exhaustive]
41pub enum LevelDbError {
42    /// Filesystem or OS I/O failure.
43    #[error("I/O error while {operation}{}: {source}", path_suffix(path.as_deref()))]
44    Io {
45        /// Operation being performed when the error occurred.
46        operation: &'static str,
47        /// Filesystem path involved in the operation, when known.
48        path: Option<PathBuf>,
49        /// Underlying operating-system error.
50        #[source]
51        source: std::io::Error,
52    },
53    /// On-disk data was malformed or failed validation.
54    #[error("corrupt database{}: {message}", path_suffix(path.as_deref()))]
55    Corruption {
56        /// Path to the file whose bytes failed validation, when known.
57        path: Option<PathBuf>,
58        /// Human-readable corruption reason.
59        message: String,
60    },
61    /// Caller supplied invalid options or input.
62    #[error("invalid argument: {message}")]
63    InvalidArgument {
64        /// Human-readable validation failure.
65        message: String,
66    },
67    /// The requested feature is disabled or not implemented.
68    #[error("unsupported feature {feature}: {message}")]
69    Unsupported {
70        /// Feature, codec, or behavior that is unavailable.
71        feature: &'static str,
72        /// Human-readable explanation.
73        message: String,
74    },
75    /// Compression or decompression failed.
76    #[error("compression error in {codec}: {message}")]
77    Compression {
78        /// Codec or table compression family being processed.
79        codec: &'static str,
80        /// Human-readable codec error.
81        message: String,
82    },
83    /// Scan cancelled through [`crate::ScanCancelFlag`].
84    #[error("scan was cancelled")]
85    Cancelled,
86    /// A mutating operation was requested on a read-only database.
87    #[error("database is read-only")]
88    ReadOnly,
89    /// `OpenOptions::error_if_exists` rejected an existing database.
90    #[error("database already exists: {}", path.display())]
91    AlreadyExists {
92        /// Existing database directory.
93        path: PathBuf,
94    },
95    /// The requested database or required metadata file was missing.
96    #[error("database not found: {}", path.display())]
97    NotFound {
98        /// Missing database directory or metadata file.
99        path: PathBuf,
100    },
101    /// An internal lock was poisoned.
102    #[error("lock poisoned while {operation}")]
103    LockPoisoned {
104        /// Locking operation that observed poisoning.
105        operation: &'static str,
106    },
107    /// A blocking task failed to join in the optional async wrapper.
108    #[error("async runtime error: {message}")]
109    Join {
110        /// Human-readable join failure.
111        message: String,
112    },
113}
114
115impl LevelDbError {
116    /// Returns the stable category of this error.
117    #[must_use]
118    pub const fn kind(&self) -> ErrorKind {
119        match self {
120            Self::Io { .. } => ErrorKind::Io,
121            Self::Corruption { .. } => ErrorKind::Corruption,
122            Self::InvalidArgument { .. } => ErrorKind::InvalidArgument,
123            Self::Unsupported { .. } => ErrorKind::Unsupported,
124            Self::Compression { .. } => ErrorKind::Compression,
125            Self::Cancelled => ErrorKind::Cancelled,
126            Self::ReadOnly => ErrorKind::ReadOnly,
127            Self::AlreadyExists { .. } => ErrorKind::AlreadyExists,
128            Self::NotFound { .. } => ErrorKind::NotFound,
129            Self::LockPoisoned { .. } => ErrorKind::LockPoisoned,
130            Self::Join { .. } => ErrorKind::Join,
131        }
132    }
133
134    /// Returns the filesystem path associated with this error, when known.
135    #[must_use]
136    pub fn path(&self) -> Option<&Path> {
137        match self {
138            Self::Io { path, .. } | Self::Corruption { path, .. } => path.as_deref(),
139            Self::AlreadyExists { path } | Self::NotFound { path } => Some(path),
140            Self::InvalidArgument { .. }
141            | Self::Unsupported { .. }
142            | Self::Compression { .. }
143            | Self::Cancelled
144            | Self::ReadOnly
145            | Self::LockPoisoned { .. }
146            | Self::Join { .. } => None,
147        }
148    }
149
150    pub(crate) fn io(
151        operation: &'static str,
152        path: impl Into<Option<PathBuf>>,
153        source: std::io::Error,
154    ) -> Self {
155        Self::Io {
156            operation,
157            path: path.into(),
158            source,
159        }
160    }
161
162    pub(crate) fn io_at(
163        operation: &'static str,
164        path: impl Into<PathBuf>,
165        source: std::io::Error,
166    ) -> Self {
167        Self::io(operation, Some(path.into()), source)
168    }
169
170    pub(crate) fn corruption(message: impl Into<String>) -> Self {
171        Self::Corruption {
172            path: None,
173            message: message.into(),
174        }
175    }
176
177    pub(crate) fn corruption_at(path: impl Into<PathBuf>, message: impl Into<String>) -> Self {
178        Self::Corruption {
179            path: Some(path.into()),
180            message: message.into(),
181        }
182    }
183
184    pub(crate) fn invalid_argument(message: impl Into<String>) -> Self {
185        Self::InvalidArgument {
186            message: message.into(),
187        }
188    }
189
190    #[allow(
191        dead_code,
192        reason = "used by codec functions when optional features are disabled"
193    )]
194    pub(crate) fn unsupported(feature: &'static str, message: impl Into<String>) -> Self {
195        Self::Unsupported {
196            feature,
197            message: message.into(),
198        }
199    }
200
201    pub(crate) fn compression(codec: &'static str, message: impl Into<String>) -> Self {
202        Self::Compression {
203            codec,
204            message: message.into(),
205        }
206    }
207
208    pub(crate) fn already_exists(path: impl Into<PathBuf>) -> Self {
209        Self::AlreadyExists { path: path.into() }
210    }
211
212    pub(crate) fn not_found(path: impl Into<PathBuf>) -> Self {
213        Self::NotFound { path: path.into() }
214    }
215
216    pub(crate) const fn lock_poisoned(operation: &'static str) -> Self {
217        Self::LockPoisoned { operation }
218    }
219
220    #[cfg(feature = "async")]
221    pub(crate) fn join(message: impl Into<String>) -> Self {
222        Self::Join {
223            message: message.into(),
224        }
225    }
226}
227
228impl From<std::io::Error> for LevelDbError {
229    fn from(source: std::io::Error) -> Self {
230        Self::io("perform filesystem I/O", None, source)
231    }
232}
233
234fn path_suffix(path: Option<&Path>) -> String {
235    path.map(|path| format!(" at {}", path.display()))
236        .unwrap_or_default()
237}