cargo_build_dist/
errors.rs

1use std::fmt::Display;
2
3/// An error that can possibly inherit from a parent error.
4///
5/// Errors can be enriched with additional information, such as the raw output
6/// of a command or a human-friendly explanation.
7#[derive(thiserror::Error, Debug)]
8pub struct Error {
9    description: String,
10    explanation: Option<String>,
11    #[source]
12    source: Option<anyhow::Error>,
13    output: Option<String>,
14}
15
16impl Error {
17    pub fn new(description: impl Into<String>) -> Self {
18        Self {
19            description: description.into(),
20            explanation: None,
21            source: None,
22            output: None,
23        }
24    }
25
26    pub fn from_source(source: impl Into<anyhow::Error>) -> Self {
27        Self::new("").with_source(source)
28    }
29
30    pub fn with_source(mut self, source: impl Into<anyhow::Error>) -> Self {
31        self.source = Some(source.into());
32
33        self
34    }
35
36    pub fn with_explanation(mut self, explanation: impl Into<String>) -> Self {
37        self.explanation = Some(explanation.into());
38
39        self
40    }
41
42    pub fn with_output(mut self, output: impl Into<String>) -> Self {
43        self.output = Some(output.into());
44
45        self
46    }
47
48    pub fn description(&self) -> &str {
49        &self.description
50    }
51
52    pub fn source(&self) -> Option<&anyhow::Error> {
53        self.source.as_ref()
54    }
55
56    pub fn explanation(&self) -> Option<&str> {
57        self.explanation.as_deref()
58    }
59
60    pub fn output(&self) -> Option<&str> {
61        self.output.as_deref()
62    }
63
64    pub fn with_context(mut self, description: impl Into<String>) -> Self {
65        if self.description.is_empty() {
66            self.description = description.into();
67
68            self
69        } else {
70            Self::new(description).with_source(self)
71        }
72    }
73}
74
75pub(crate) trait ErrorContext {
76    fn with_context(self, description: impl Into<String>) -> Self;
77    fn with_full_context(
78        self,
79        description: impl Into<String>,
80        explanation: impl Into<String>,
81    ) -> Self;
82}
83
84impl<T> ErrorContext for Result<T> {
85    fn with_context(self, description: impl Into<String>) -> Self {
86        self.map_err(|e| e.with_context(description))
87    }
88
89    fn with_full_context(
90        self,
91        description: impl Into<String>,
92        explanation: impl Into<String>,
93    ) -> Self {
94        self.map_err(|e| e.with_context(description).with_explanation(explanation))
95    }
96}
97
98impl Display for Error {
99    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
100        write!(f, "{}", self.description)?;
101
102        if let Some(source) = self.source.as_ref() {
103            write!(f, ": {}", source)?;
104        }
105
106        if let Some(explanation) = &self.explanation {
107            write!(f, "\n\n{}", explanation)?;
108        }
109
110        Ok(())
111    }
112}
113
114/// A convenience type alias to return `Error`s from functions.
115pub type Result<T> = std::result::Result<T, Error>;