foyer_common/
error.rs

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