jxoesneon_tectonic_errors/
lib.rs

1// Copyright 2020 the Tectonic Project.
2// Licensed under the MIT License.
3
4//! Generic error handling for Tectonic.
5//!
6//! This crate provides a generic boxed error type, plus supporting utilities.
7//! In particular:
8//!
9//! - The basic `Error` type is an `anyhow` 1.x boxed Error
10//! - The `atry!` macro allows simple structured annotations to be added to `?`
11//!   operations
12//! - The `a_ok_or!` macro allows for annotations to `Option::ok_or_else` calls.
13
14use std::{error, fmt, result::Result as StdResult};
15
16/// The generic error type, for complex operations that can fail for a wide
17/// range of reasons. This type is a reexport of the `anyhow` 1.x series Error
18/// type.
19///
20/// There’s an appeal to not explicitly committing ourselves to using this
21/// particular error implementation, but the `anyhow` error type has a
22/// sufficient number of special methods and traits that it would be pretty
23/// tedious to re-implement them all while pretending that we're using some
24/// different type.
25pub use anyhow::Error;
26
27/// A preloaded result type where the error type is our generic error type.
28pub use anyhow::Result;
29
30/// The specific version of `anyhow` used by this crate.
31pub use anyhow;
32
33/// A simple annotated message that can be attached to errors using the
34/// `anyhow::Context` methods, or be used as an error type itself. The
35/// recommended way to use this for error context is using `atry!` or related
36/// macros.
37///
38/// The `std::fmt::Display` of this type yields its primary "message". Consumers
39/// that are aware of this type can obtain additional context through its
40/// `notes()` method.
41#[derive(Debug, Default)]
42pub struct AnnotatedMessage {
43    /// The main message.
44    message: String,
45
46    /// Additional annotations that can be displayed after the primary message.
47    notes: Vec<String>,
48}
49
50impl AnnotatedMessage {
51    /// Set the primary message associated with this annotated report.
52    pub fn set_message<T: fmt::Display>(&mut self, m: T) {
53        self.message = format!("{m}");
54    }
55
56    /// Add an additional note to be associated with this annotated report.
57    pub fn add_note<T: fmt::Display>(&mut self, n: T) {
58        self.notes.push(format!("{n}"));
59    }
60
61    /// Obtain the set of notes associated with this report.
62    pub fn notes(&self) -> &[String] {
63        &self.notes[..]
64    }
65}
66
67impl fmt::Display for AnnotatedMessage {
68    fn fmt(&self, f: &mut fmt::Formatter) -> StdResult<(), fmt::Error> {
69        write!(f, "{}", self.message)
70    }
71}
72
73impl error::Error for AnnotatedMessage {}
74
75/// "Annotated try" — like `try!`, but with the ability to add extended context
76/// to the error message. This tries to provide a bit more syntactic sugar than
77/// anyhow's `with_context()`, and it supports our AnnotatedMessage context type.
78///
79/// # Example
80///
81/// ```
82/// # use tectonic_errors::{atry, Result};
83/// # fn my_fallible_operation(a: bool, b: bool) -> Result<()> { Ok(()) }
84/// # fn my_caller() -> Result<()> {
85/// # let arg = true;
86/// # let option = true;
87/// let ok_val = atry!(
88///     my_fallible_operation(arg, option);
89///     ["the operation on `{}` failed", arg]
90///     (note "option was `{}`; maybe you should choose a better value next time", option)
91/// );
92/// # Ok(())
93/// # }
94/// ```
95///
96/// This is equivalent to `let ok_val = my_fallible_operation(arg)?;`, but if the
97/// operation fails, the returned error will have a message formatted as per the
98/// format expression in square brackets, with attached "note" text formatted as
99/// per the parenthesized `note` expression. There may be zero, one, or many notes
100/// attached.
101#[macro_export]
102macro_rules! atry {
103    (@aa $ar:ident [ $($inner:tt)+ ] ) => {
104        $ar.set_message(format!($($inner)+));
105    };
106
107    (@aa $ar:ident ( note $($inner:tt)+ ) ) => {
108        $ar.add_note(format!($($inner)+));
109    };
110
111    ($op:expr ; $( $annotation:tt )+) => {{
112        use $crate::anyhow::Context;
113        $op.with_context(|| {
114            let mut ar = $crate::AnnotatedMessage::default();
115            $(
116                atry!(@aa ar $annotation);
117            )+
118            ar
119        })?
120    }};
121}
122
123/// "annotated ok_or" — like `Option::ok_or_else()?`, but with the ability to
124/// add extended context to the error. This yields an `AnnotatedMessage` as its
125/// error type.
126#[macro_export]
127macro_rules! a_ok_or {
128    ($option:expr ; $( $annotation:tt )+) => {{
129        $option.ok_or_else(|| {
130            let mut ar = $crate::AnnotatedMessage::default();
131            $(
132                $crate::atry!(@aa ar $annotation);
133            )+
134            ar
135        })?
136    }};
137}
138
139/// A "prelude" module providing a collection of useful names, without
140/// causing compiler complaints about the ones you don’t use.
141///
142/// Provided names are:
143///
144/// - The core `anyhow::Error` type
145/// - The core `anyhow::Result` type, which is `Result<T, anyhow::Error>`
146/// - The `anyhow!` macro to create an Error value from a string-format expression
147/// - The `bail!` macro: `bail!(...)` = `return Err(anyhow!(...))`
148/// - The `ensure!` macro: `ensure!(cond, err)` = `if !cond { bail!(err); }`
149/// - The `atry!` macro for annotated question-mark behavior
150/// - The `a_ok_or!` macro for annotated, fallibale Option unwrapping
151/// - Rust's `std::result::Result` type aliased as StdResult for convenience
152pub mod prelude {
153    pub use crate::{a_ok_or, atry};
154    pub use anyhow::{anyhow, bail, ensure, Error, Result};
155    pub use std::result::Result as StdResult;
156}
157
158#[cfg(test)]
159mod test {
160    use super::*;
161
162    fn atry_eval() -> Result<()> {
163        let fine: Result<usize> = Ok(0);
164        let display = "a string";
165
166        atry!(
167            fine;
168            ["message {}", display]
169            (note "but what about {}", display)
170        );
171        Ok(())
172    }
173
174    #[test]
175    fn atry_compile() {
176        atry_eval().unwrap();
177    }
178
179    fn a_ok_or_eval() -> Result<()> {
180        let fine: Option<usize> = Some(0);
181        let display = "a string";
182
183        a_ok_or!(
184            fine;
185            ["message {}", display]
186            (note "but what about {}", display)
187        );
188        Ok(())
189    }
190
191    #[test]
192    fn a_ok_or_compile() {
193        a_ok_or_eval().unwrap();
194    }
195}