Skip to main content

foyer_common/
error.rs

1// Copyright 2026 foyer Project Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::{
16    backtrace::Backtrace,
17    fmt::{Debug, Display},
18    sync::Arc,
19};
20
21/// ErrorKind is all kinds of Error of foyer.
22#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
23pub enum ErrorKind {
24    /// I/O error.
25    Io,
26    /// External error.
27    External,
28    /// Config error.
29    Config,
30    /// Channel closed.
31    ChannelClosed,
32    /// Task cancelled.
33    TaskCancelled,
34    /// Join error.
35    Join,
36    /// Parse error.
37    Parse,
38    /// Buffer size limit.
39    ///
40    /// Not a real error.
41    ///
42    /// Indicates that the buffer size has exceeded the limit and the caller may allocate a larger buffer and retry.
43    BufferSizeLimit,
44    /// Checksum mismatch.
45    ChecksumMismatch,
46    /// Magic mismatch.
47    MagicMismatch,
48    /// Out of range.
49    OutOfRange,
50    /// No space.
51    NoSpace,
52    /// Closed.
53    Closed,
54    /// Recover error.
55    Recover,
56    /// Unsupported operation.
57    Unsupported,
58}
59
60impl ErrorKind {
61    /// Convert self into static str.
62    pub fn into_static(self) -> &'static str {
63        self.into()
64    }
65}
66
67impl Display for ErrorKind {
68    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
69        write!(f, "{}", self.into_static())
70    }
71}
72
73impl From<ErrorKind> for &'static str {
74    fn from(v: ErrorKind) -> &'static str {
75        match v {
76            ErrorKind::Io => "I/O error",
77            ErrorKind::External => "External error",
78            ErrorKind::Config => "Config error",
79            ErrorKind::ChannelClosed => "Channel closed",
80            ErrorKind::TaskCancelled => "Task cancelled",
81            ErrorKind::Join => "Join error",
82            ErrorKind::Parse => "Parse error",
83            ErrorKind::BufferSizeLimit => "Buffer size limit exceeded",
84            ErrorKind::ChecksumMismatch => "Checksum mismatch",
85            ErrorKind::MagicMismatch => "Magic mismatch",
86            ErrorKind::OutOfRange => "Out of range",
87            ErrorKind::NoSpace => "No space",
88            ErrorKind::Closed => "Closed",
89            ErrorKind::Recover => "Recover error",
90            ErrorKind::Unsupported => "Unsupported operation",
91        }
92    }
93}
94
95/// Error is the error struct returned by all foyer functions.
96///
97/// ## Display
98///
99/// Error can be displayed in two ways:
100///
101/// - Via `Display`: like `err.to_string()` or `format!("{err}")`
102///
103/// Error will be printed in a single line:
104///
105/// ```shell
106/// External error, context: { k1: v2, k2: v2 } => external error, source: TestError: test error
107/// ```
108///
109/// - Via `Debug`: like `format!("{err:?}")`
110///
111/// Error will be printed in multi lines with more details and backtraces (if captured):
112///
113/// ```shell
114/// External error => external error
115///
116/// Context:
117///   k1: v2
118///   k2: v2
119///
120/// Source:
121///   TestError: test error
122///
123/// Backtrace:
124///    0: foyer_common::error::Error::new
125///              at ./src/error.rs:259:38
126///    1: foyer_common::error::tests::test_error_format
127///              at ./src/error.rs:481:17
128///    2: foyer_common::error::tests::test_error_format::{{closure}}
129///              at ./src/error.rs:480:27
130///    ...
131/// ```
132///
133/// - For conventional struct-style Debug representation, like `format!("{err:#?}")`:
134///
135/// ```shell
136/// Error {
137///     kind: External,
138///     message: "external error",
139///     context: [
140///         (
141///             "k1",
142///             "v2",
143///         ),
144///         (
145///             "k2",
146///             "v2",
147///         ),
148///     ],
149///     source: Some(
150///         TestError(
151///             "test error",
152///         ),
153///     ),
154///     backtrace: Some(
155///         Backtrace [
156///             { fn: "foyer_common::error::Error::new", file: "./src/error.rs", line: 259 },
157///             { fn: "foyer_common::error::tests::test_error_format", file: "./src/error.rs", line: 481 },
158///             ...
159///         ],
160///     ),
161/// }
162/// ```
163pub struct Error {
164    kind: ErrorKind,
165    message: String,
166
167    context: Vec<(&'static str, String)>,
168
169    source: Option<Arc<anyhow::Error>>,
170    backtrace: Option<Arc<Backtrace>>,
171}
172
173impl Debug for Error {
174    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
175        // If alternate has been specified, we will print like Debug.
176        if f.alternate() {
177            let mut de = f.debug_struct("Error");
178            de.field("kind", &self.kind);
179            de.field("message", &self.message);
180            de.field("context", &self.context);
181            de.field("source", &self.source);
182            de.field("backtrace", &self.backtrace);
183            return de.finish();
184        }
185
186        write!(f, "{}", self.kind)?;
187        if !self.message.is_empty() {
188            write!(f, " => {}", self.message)?;
189        }
190        writeln!(f)?;
191
192        if !self.context.is_empty() {
193            writeln!(f)?;
194            writeln!(f, "Context:")?;
195            for (k, v) in self.context.iter() {
196                writeln!(f, "  {}: {}", k, v)?;
197            }
198        }
199
200        if let Some(source) = &self.source {
201            writeln!(f)?;
202            writeln!(f, "Source:")?;
203            writeln!(f, "  {source:#}")?;
204        }
205
206        if let Some(backtrace) = &self.backtrace {
207            writeln!(f)?;
208            writeln!(f, "Backtrace:")?;
209            writeln!(f, "{backtrace}")?;
210        }
211
212        Ok(())
213    }
214}
215
216impl Display for Error {
217    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
218        write!(f, "{}", self.kind)?;
219
220        if !self.context.is_empty() {
221            write!(f, ", context: {{ ")?;
222            let mut iter = self.context.iter().peekable();
223            while let Some((k, v)) = iter.next() {
224                write!(f, "{}: {}", k, v)?;
225                if iter.peek().is_some() {
226                    write!(f, ", ")?;
227                }
228            }
229            write!(f, " }}")?;
230        }
231
232        if !self.message.is_empty() {
233            write!(f, " => {}", self.message)?;
234        }
235
236        if let Some(source) = &self.source {
237            write!(f, ", source: {source}")?;
238        }
239
240        Ok(())
241    }
242}
243
244impl std::error::Error for Error {
245    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
246        self.source.as_ref().map(|v| v.as_ref().as_ref())
247    }
248}
249
250/// Cloning an [`Error`] with large message and context can be expensive.
251///
252/// Be careful when cloning errors in performance-critical paths.
253impl Clone for Error {
254    fn clone(&self) -> Self {
255        Self {
256            kind: self.kind,
257            message: self.message.clone(),
258            context: self.context.clone(),
259            source: self.source.clone(),
260            backtrace: self.backtrace.clone(),
261        }
262    }
263}
264
265impl Error {
266    /// Create a new error.
267    ///
268    /// If the error needs to carry a source error, please use `with_source` method.
269    ///
270    /// For example"
271    ///
272    /// ```rust
273    /// # use foyer_common::error::{Error, ErrorKind};
274    /// let io_error = std::io::Error::other("an I/O error occurred");
275    /// Error::new(ErrorKind::Io, "an external error occurred").with_source(io_error);
276    /// ```
277    pub fn new(kind: ErrorKind, message: impl Into<String>) -> Self {
278        Self {
279            kind,
280            message: message.into(),
281            context: Vec::new(),
282            source: None,
283            backtrace: Some(Arc::new(Backtrace::capture())),
284        }
285    }
286
287    /// Add more context in error.
288    pub fn with_context(mut self, key: &'static str, value: impl ToString) -> Self {
289        self.context.push((key, value.to_string()));
290        self
291    }
292
293    /// Set source for error.
294    ///
295    /// # Notes
296    ///
297    /// If the source has been set, we will raise a panic here.
298    pub fn with_source(mut self, source: impl Into<anyhow::Error>) -> Self {
299        debug_assert!(self.source.is_none(), "the source error has been set");
300        self.source = Some(Arc::new(source.into()));
301        self
302    }
303
304    /// Get the error kind.
305    pub fn kind(&self) -> ErrorKind {
306        self.kind
307    }
308
309    /// Get the error message.
310    pub fn message(&self) -> &str {
311        &self.message
312    }
313
314    /// Get the error context.
315    pub fn context(&self) -> &Vec<(&'static str, String)> {
316        &self.context
317    }
318
319    /// Get the error backtrace.
320    pub fn backtrace(&self) -> Option<&Backtrace> {
321        self.backtrace.as_deref()
322    }
323
324    /// Get the error source.
325    pub fn source(&self) -> Option<&anyhow::Error> {
326        self.source.as_deref()
327    }
328
329    /// Downcast the reference of the source error to a specific error type reference.
330    pub fn downcast_ref<E>(&self) -> Option<&E>
331    where
332        E: std::error::Error + Send + Sync + 'static,
333    {
334        self.source.as_deref().and_then(|e| e.downcast_ref::<E>())
335    }
336}
337
338/// Result type for foyer.
339pub type Result<T> = std::result::Result<T, Error>;
340
341/// Helper methods for Error.
342impl Error {
343    /// Helper for creating an [`ErrorKind::Io`] error from a raw OS error code.
344    pub fn raw_os_io_error(raw: i32) -> Self {
345        let source = std::io::Error::from_raw_os_error(raw);
346        Self::io_error(source)
347    }
348
349    /// Helper for creating an [`ErrorKind::Io`] error from [`std::io::Error`].
350    pub fn io_error(source: std::io::Error) -> Self {
351        match source.kind() {
352            std::io::ErrorKind::WriteZero => Error::new(ErrorKind::BufferSizeLimit, "coding error").with_source(source),
353            _ => Error::new(ErrorKind::Io, "coding error").with_source(source),
354        }
355    }
356
357    /// Helper for creating an error from [`bincode::Error`].
358    #[cfg(feature = "serde")]
359    pub fn bincode_error(source: bincode::Error) -> Self {
360        match *source {
361            bincode::ErrorKind::SizeLimit => Error::new(ErrorKind::BufferSizeLimit, "coding error").with_source(source),
362            bincode::ErrorKind::Io(e) => Self::io_error(e),
363            _ => Error::new(ErrorKind::External, "coding error").with_source(source),
364        }
365    }
366
367    /// Helper for creating a [`ErrorKind::NoSpace`] error with context.
368    pub fn no_space(capacity: usize, allocated: usize, required: usize) -> Self {
369        Error::new(ErrorKind::NoSpace, "not enough space left")
370            .with_context("capacity", capacity)
371            .with_context("allocated", allocated)
372            .with_context("required", required)
373    }
374}
375
376impl From<std::io::Error> for Error {
377    fn from(e: std::io::Error) -> Self {
378        Self::io_error(e)
379    }
380}
381
382#[cfg(feature = "serde")]
383impl From<bincode::Error> for Error {
384    fn from(e: bincode::Error) -> Self {
385        Self::bincode_error(e)
386    }
387}
388
389#[cfg(test)]
390mod tests {
391
392    use super::*;
393
394    fn is_send_sync_static<T: Send + Sync + 'static>() {}
395
396    #[test]
397    fn test_send_sync_static() {
398        is_send_sync_static::<Error>();
399    }
400
401    #[derive(Debug, Clone, PartialEq, Eq)]
402    struct TestError(String);
403
404    impl std::fmt::Display for TestError {
405        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
406            write!(f, "TestError: {}", self.0)
407        }
408    }
409
410    impl std::error::Error for TestError {}
411
412    #[test]
413    fn test_error_display() {
414        let io_error = std::io::Error::other("some I/O error");
415        let err = Error::new(ErrorKind::Io, "an I/O error occurred")
416            .with_source(io_error)
417            .with_context("k1", "v1")
418            .with_context("k2", "v2");
419
420        assert_eq!(
421            "I/O error, context: { k1: v1, k2: v2 } => an I/O error occurred, source: some I/O error",
422            err.to_string()
423        );
424    }
425
426    #[test]
427    fn test_error_downcast() {
428        let inner = TestError("Error or not error, that is a question.".to_string());
429        let err = Error::new(ErrorKind::External, "").with_source(inner.clone());
430
431        let downcasted = err.downcast_ref::<TestError>().unwrap();
432        assert_eq!(downcasted, &inner);
433    }
434
435    #[test]
436    fn test_error_format() {
437        let e = Error::new(ErrorKind::External, "external error")
438            .with_context("k1", "v2")
439            .with_context("k2", "v2")
440            .with_source(TestError("test error".into()));
441
442        println!("========== BEGIN DISPLAY FORMAT ==========");
443        println!("{e}");
444        println!("========== END DISPLAY FORMAT ==========");
445
446        println!();
447
448        println!("========== BEGIN DEBUG FORMAT ==========");
449        println!("{e:?}");
450        println!("========== END DEBUG FORMAT ==========");
451
452        println!();
453
454        println!("========== BEGIN DEBUG FORMAT (PRETTY) ==========");
455        println!("{e:#?}");
456        println!("========== END DEBUG FORMAT (PRETTY) ==========");
457    }
458}