Skip to main content

logforth_core/
error.rs

1// Copyright 2024 FastLabs Developers
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::fmt;
16use std::io;
17
18/// The error struct of logforth.
19pub struct Error {
20    message: String,
21    source: Option<anyhow::Error>,
22    context: Vec<(&'static str, String)>,
23}
24
25impl fmt::Display for Error {
26    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
27        write!(f, "{}", self.message)?;
28
29        if !self.context.is_empty() {
30            write!(f, ", context: {{ ")?;
31            write!(
32                f,
33                "{}",
34                self.context
35                    .iter()
36                    .map(|(k, v)| format!("{k}: {v}"))
37                    .collect::<Vec<_>>()
38                    .join(", ")
39            )?;
40            write!(f, " }}")?;
41        }
42
43        if let Some(source) = &self.source {
44            write!(f, ", source: {source}")?;
45        }
46
47        Ok(())
48    }
49}
50
51impl fmt::Debug for Error {
52    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
53        // If alternate has been specified, we will print like Debug.
54        if f.alternate() {
55            let mut de = f.debug_struct("Error");
56            de.field("message", &self.message);
57            de.field("context", &self.context);
58            de.field("sources", &self.source);
59            return de.finish();
60        }
61
62        write!(f, "{}", self.message)?;
63        writeln!(f)?;
64
65        if !self.context.is_empty() {
66            writeln!(f)?;
67            writeln!(f, "Context:")?;
68            for (k, v) in self.context.iter() {
69                writeln!(f, "   {k}: {v}")?;
70            }
71        }
72
73        if let Some(source) = &self.source {
74            writeln!(f)?;
75            writeln!(f, "Source:")?;
76            writeln!(f, "   {source:#}")?;
77        }
78
79        Ok(())
80    }
81}
82
83impl std::error::Error for Error {
84    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
85        self.source.as_ref().map(|v| v.as_ref())
86    }
87}
88
89impl Error {
90    /// Create a new Error with error kind and message.
91    pub fn new(message: impl Into<String>) -> Self {
92        Self {
93            message: message.into(),
94            source: None,
95            context: vec![],
96        }
97    }
98
99    /// Add one more context in error.
100    pub fn with_context(mut self, key: &'static str, value: impl ToString) -> Self {
101        self.context.push((key, value.to_string()));
102        self
103    }
104
105    /// Set source for error.
106    ///
107    /// # Panics
108    ///
109    /// If the source has been set, we will raise a panic here.
110    pub fn with_source(mut self, src: impl Into<anyhow::Error>) -> Self {
111        assert!(self.source.is_none(), "the source error has been set");
112
113        self.source = Some(src.into());
114        self
115    }
116
117    /// Return an iterator over all sources of this error.
118    pub fn sources(&self) -> impl ExactSizeIterator<Item = &(dyn std::error::Error + 'static)> {
119        self.source.iter().map(|v| v.as_ref())
120    }
121
122    /// Default constructor for [`Error`] from [`io::Error`].
123    pub fn from_io_error(err: io::Error) -> Error {
124        Error::new("failed to perform io").with_source(err)
125    }
126
127    /// Default constructor for [`Error`] from [`fmt::Error`].
128    pub fn from_fmt_error(err: fmt::Error) -> Error {
129        Error::new("failed to perform format").with_source(err)
130    }
131}