summaryrefslogtreecommitdiff
path: root/src/error/chain.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/error/chain.rs')
-rw-r--r--src/error/chain.rs72
1 files changed, 72 insertions, 0 deletions
diff --git a/src/error/chain.rs b/src/error/chain.rs
new file mode 100644
index 0000000..d78fe67
--- /dev/null
+++ b/src/error/chain.rs
@@ -0,0 +1,72 @@
+use std::{error::Error, io};
+
+use super::Id;
+
+pub fn format<W, E>(out: &mut W, error: E) -> Result<(), io::Error>
+where
+ W: io::Write,
+ E: Error,
+{
+ writeln!(out, "{error}")?;
+ format_sources(out, error)?;
+
+ Ok(())
+}
+
+pub fn format_with_id<W, E>(out: &mut W, id: &Id, error: E) -> Result<(), io::Error>
+where
+ W: io::Write,
+ E: Error,
+{
+ writeln!(out, "[{id}] {error}")?;
+ format_sources(out, error)?;
+
+ Ok(())
+}
+
+fn format_sources<W, E>(out: &mut W, error: E) -> Result<(), io::Error>
+where
+ W: io::Write,
+ E: Error,
+{
+ let mut sources = Sources::from(&error);
+ if let Some(source) = sources.next() {
+ writeln!(out)?;
+ writeln!(out, "Caused by:")?;
+ writeln!(out, " {source}")?;
+ for source in sources {
+ writeln!(out, " {source}")?;
+ }
+ writeln!(out)?;
+ }
+ Ok(())
+}
+
+struct Sources<'e> {
+ next: Option<&'e dyn Error>,
+}
+
+impl<'e, E> From<&'e E> for Sources<'e>
+where
+ E: Error,
+{
+ fn from(error: &'e E) -> Self {
+ Self {
+ next: error.source(),
+ }
+ }
+}
+
+// See also: <https://doc.rust-lang.org/std/error/trait.Error.html#method.sources> However, we only
+// want to iterate the sources, and not the error itself. Personally, I find the `skip(1)`
+// suggestion untidy, since the error itself is non-optional while the sources are optional.
+impl<'a> Iterator for Sources<'a> {
+ type Item = &'a dyn Error;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ let source = self.next;
+ self.next = self.next.and_then(|err| err.source());
+
+ source
+ }
+}