expect_exit/
lib.rs

1#![deny(missing_docs)]
2#![deny(clippy::missing_docs_in_private_items)]
3#![doc(html_root_url = "https://docs.rs/expect-exit/0.5.3")]
4#![deprecated(
5    note = "This module is in maintenance mode; the `anyhow` library might be a better choice"
6)]
7// SPDX-FileCopyrightText: Peter Pentchev <roam@ringlet.net>
8// SPDX-License-Identifier: BSD-2-Clause
9//! Display an error message and exit without a panic.
10//!
11//! The `expect-exit` library defines the [`Expected`], [`ExpectedWithError`],
12//! and [`ExpectedResult`] traits and implements them for the standard
13//! [`Result`][`std::result::Result`], [`Option`][`std::option::Option`], and
14//! [`bool`][`std::primitive::bool`] types as appropriate.
15//! This allows a program to display an error message
16//! and exit with a non-zero exit code without invoking a Rust panic, yet
17//! optionally unwinding the stack so that various objects may perform some
18//! clean-up actions.
19//!
20//! The methods with an `_e` suffix append an appropriate error message to
21//! the supplied one. The methods with a `_` suffix allow the caller to
22//! specify an already-constructed message instead of a function that
23//! returns it.
24//!
25//! ```rust
26//! # #![allow(deprecated)]
27//! use std::collections::HashMap;
28//! use std::env;
29//!
30//! use expect_exit::{Expected, ExpectedWithError};
31//!
32//! let mut vars: HashMap<String, String> = ["HOME", "PATH", "LOGNAME"]
33//!     .iter()
34//!     .map(|name| {
35//!         (
36//!             name.to_string(),
37//!             env::var(name).or_exit_e(|| format!("No '{} in the environment", name)),
38//!         )
39//!     })
40//!     .collect();
41//! vars.insert(
42//!     "PWD".to_string(),
43//!     env::current_dir()
44//!         .or_exit_e_("Could not determine the current directory")
45//!         .to_str()
46//!         .or_exit_("Could not represent the path to the current directory")
47//!         .to_string(),
48//! );
49//! println!("{:?}", vars);
50//! if !vars["PWD"].starts_with("/") {
51//!     expect_exit::exit("Expected an absolute path to the current directory");
52//! }
53//! ```
54//!
55//! The traits are currently implemented for the standard
56//! [`Result`][`std::result::Result`],
57//! [`Option`][`std::option::Option`], and
58//! [`bool`][`std::primitive::bool`] types as appropriate.
59//!
60//! For the crate's change history, see
61//! the [NEWS.md](https://gitlab.com/ppentchev/expect-exit/-/blob/master/NEWS.md)
62//! file in the source distribution.
63
64use std::error::Error;
65use std::fmt::{Display, Formatter, Result as FmtResult};
66use std::panic;
67use std::process;
68
69/// Something to enqueue a [`Drop::drop()`] call to ensure that we unwind
70/// the stack before exiting the process.
71struct ExitHelper {
72    /// The code to exit the program with after the stack has been unwound.
73    code: i32,
74}
75
76impl Drop for ExitHelper {
77    fn drop(&mut self) {
78        process::exit(self.code);
79    }
80}
81
82/// Unwind the stack and end the process with the specified exit code.
83#[inline]
84pub fn exit_unwind(code: i32) -> ! {
85    panic::resume_unwind(Box::new(ExitHelper { code }));
86}
87
88/// Output a message, then invoke the specified exit routine.
89#[allow(clippy::print_stderr)]
90fn _die(msg: &str, exit: fn(i32) -> !) -> ! {
91    eprintln!("{msg}");
92    exit(1);
93}
94
95/// Output a message containing an error description, then invoke the specified exit routine.
96#[allow(clippy::print_stderr)]
97fn _die_perror(msg: &str, err: impl Display, exit: fn(i32) -> !) -> ! {
98    eprintln!("{msg}: {err}");
99    exit(1);
100}
101
102/// Display the specified message, then unwind the stack and exit.
103#[inline]
104pub fn exit(msg: &str) -> ! {
105    _die(msg, exit_unwind);
106}
107
108/// Display the specified message and append an appropriate
109/// description of the error, then unwind the stack and exit.
110#[inline]
111pub fn exit_perror<E: Display>(msg: &str, err: E) -> ! {
112    _die_perror(msg, err, exit_unwind);
113}
114
115/// Display the specified message, then exit without unwinding
116/// the stack.
117#[inline]
118pub fn die(msg: &str) -> ! {
119    _die(msg, process::exit);
120}
121
122/// Display the specified message and append an appropriate
123/// description of the error, then exit without unwinding
124/// the stack.
125#[inline]
126pub fn die_perror<E: Display>(msg: &str, err: E) -> ! {
127    _die_perror(msg, err, process::exit);
128}
129
130/// The error object returned by the [`ExpectedResult`] methods.
131#[derive(Debug)]
132pub struct ExpectationFailed {
133    /// The error message describing the failure that occurred.
134    message: String,
135}
136
137impl Display for ExpectationFailed {
138    #[inline]
139    #[allow(clippy::min_ident_chars)]
140    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
141        write!(f, "{msg}", msg = self.message)
142    }
143}
144
145impl Error for ExpectationFailed {}
146
147/// Unwrap or exit with the specified message.
148pub trait Expected<T>
149where
150    Self: Sized,
151{
152    /// Test the value. On success, return the unwrapped value.
153    /// On error, unwind the stack, display the specified message,
154    /// and exit.
155    #[inline]
156    fn or_exit_(self, msg: &str) -> T {
157        self.or_exit(|| msg.to_owned())
158    }
159
160    /// Test the value. On success, return the unwrapped value.
161    /// On error, unwind the stack, call the supplied function to
162    /// obtain an error message, display it,
163    /// and exit.
164    fn or_exit<F>(self, msgf: F) -> T
165    where
166        F: FnOnce() -> String;
167
168    /// Test the value. On success, return the unwrapped value.
169    /// On error, do not unwind the stack, display the specified message,
170    /// and exit.
171    #[inline]
172    fn or_die_(self, msg: &str) -> T {
173        self.or_die(|| msg.to_owned())
174    }
175
176    /// Test the value. On success, return the unwrapped value.
177    /// On error, do not unwind the stack, call the supplied function to
178    /// obtain an error message, display it,
179    /// and exit.
180    fn or_die<F>(self, msgf: F) -> T
181    where
182        F: FnOnce() -> String;
183
184    /// Alias for [`Expected::or_exit_`].
185    #[inline]
186    fn expect_or_exit_(self, msg: &str) -> T {
187        self.or_exit_(msg)
188    }
189
190    /// Alias for [`Expected::or_exit`].
191    #[inline]
192    fn expect_or_exit<F>(self, msgf: F) -> T
193    where
194        F: FnOnce() -> String,
195    {
196        self.or_exit(msgf)
197    }
198
199    /// Alias for [`Expected::or_die_`].
200    #[inline]
201    fn expect_or_die_(self, msg: &str) -> T {
202        self.or_die_(msg)
203    }
204
205    /// Alias for [`Expected::or_die`].
206    #[inline]
207    fn expect_or_die<F>(self, msgf: F) -> T
208    where
209        F: FnOnce() -> String,
210    {
211        self.or_die(msgf)
212    }
213}
214
215/// Unwrap or exit with an appropriate error message.
216pub trait ExpectedWithError<T>: Expected<T> {
217    /// Test the value. On success, return the unwrapped value.
218    /// On error, unwind the stack, display the specified message,
219    /// append an appropriate description of the error, and exit.
220    #[inline]
221    fn or_exit_e_(self, msg: &str) -> T {
222        self.or_exit_e(|| msg.to_owned())
223    }
224
225    /// Test the value. On success, return the unwrapped value.
226    /// On error, unwind the stack, call the supplied function to
227    /// obtain an error message, display it,
228    /// append an appropriate description of the error, and exit.
229    fn or_exit_e<F>(self, msgf: F) -> T
230    where
231        F: FnOnce() -> String;
232
233    /// Test the value. On success, return the unwrapped value.
234    /// On error, do not unwind the stack, display the specified message,
235    /// append an appropriate description of the error, and exit.
236    #[inline]
237    fn or_die_e_(self, msg: &str) -> T {
238        self.or_die_e(|| msg.to_owned())
239    }
240
241    /// Test the value. On success, return the unwrapped value.
242    /// On error, do not unwind the stack, call the supplied function to
243    /// obtain an error message, display it,
244    /// append an appropriate description of the error, and exit.
245    fn or_die_e<F>(self, msgf: F) -> T
246    where
247        F: FnOnce() -> String;
248
249    /// Alias for [`ExpectedWithError::or_exit_e_`].
250    #[inline]
251    fn expect_or_exit_perror_(self, msg: &str) -> T {
252        self.or_exit_e_(msg)
253    }
254
255    /// Alias for [`ExpectedWithError::or_exit_e`].
256    #[inline]
257    fn expect_or_exit_perror<F>(self, msgf: F) -> T
258    where
259        F: FnOnce() -> String,
260    {
261        self.or_exit_e(msgf)
262    }
263
264    /// Alias for [`ExpectedWithError::or_die_e_`].
265    #[inline]
266    fn expect_or_die_perror_(self, msg: &str) -> T {
267        self.or_die_e_(msg)
268    }
269
270    /// Alias for [`ExpectedWithError::or_die_e`].
271    #[inline]
272    fn expect_or_die_perror<F>(self, msgf: F) -> T
273    where
274        F: FnOnce() -> String,
275    {
276        self.or_die_e(msgf)
277    }
278}
279
280/// Test the value and return a result object containing either
281/// the inner value or an error object that, when displayed, will provide
282/// the specified error message.
283pub trait ExpectedResult<T>
284where
285    Self: Sized,
286{
287    /// Return a result object with a non-boxed `Error` object.
288    ///
289    /// # Errors
290    /// [`ExpectationFailed`] on error.
291    #[inline]
292    fn expect_result_nb_(self, msg: &str) -> Result<T, ExpectationFailed> {
293        self.expect_result_nb(|| msg.to_owned())
294    }
295
296    /// Return a result object that may be tested using the `?` operator.
297    ///
298    /// # Errors
299    /// [`ExpectationFailed`] on error.
300    #[inline]
301    fn expect_result_(self, msg: &str) -> Result<T, Box<dyn Error>> {
302        self.expect_result(|| msg.to_owned())
303    }
304
305    /// Return a result object with a non-boxed `Error` object.
306    /// Invoke the specified function to obtain the error message.
307    ///
308    /// # Errors
309    /// [`ExpectationFailed`] on error.
310    fn expect_result_nb<F>(self, msgf: F) -> Result<T, ExpectationFailed>
311    where
312        F: FnOnce() -> String;
313
314    /// Return a result object that may be tested using the `?` operator.
315    /// Invoke the specified function to obtain the error message.
316    ///
317    /// # Errors
318    /// [`ExpectationFailed`] on error.
319    #[inline]
320    fn expect_result<F>(self, msgf: F) -> Result<T, Box<dyn Error>>
321    where
322        F: FnOnce() -> String,
323    {
324        self.expect_result_nb(msgf)
325            .map_err(|err| -> Box<dyn Error> { Box::new(err) })
326    }
327}
328
329impl<T> Expected<T> for Option<T> {
330    #[inline]
331    fn or_exit<F>(self, msgf: F) -> T
332    where
333        F: FnOnce() -> String,
334    {
335        self.unwrap_or_else(|| exit(&msgf()))
336    }
337
338    #[inline]
339    fn or_die<F>(self, msgf: F) -> T
340    where
341        F: FnOnce() -> String,
342    {
343        self.unwrap_or_else(|| die(&msgf()))
344    }
345}
346
347impl<T, E> Expected<T> for Result<T, E>
348where
349    E: Display,
350{
351    #[inline]
352    fn or_exit<F>(self, msgf: F) -> T
353    where
354        F: FnOnce() -> String,
355    {
356        self.unwrap_or_else(|_| exit(&msgf()))
357    }
358
359    #[inline]
360    fn or_die<F>(self, msgf: F) -> T
361    where
362        F: FnOnce() -> String,
363    {
364        self.unwrap_or_else(|_| die(&msgf()))
365    }
366}
367
368impl<T, E> ExpectedWithError<T> for Result<T, E>
369where
370    E: Display,
371{
372    #[inline]
373    fn or_exit_e<F>(self, msgf: F) -> T
374    where
375        F: FnOnce() -> String,
376    {
377        match self {
378            Err(err) => exit_perror(&msgf(), err),
379            Ok(value) => value,
380        }
381    }
382
383    #[inline]
384    fn or_die_e<F>(self, msgf: F) -> T
385    where
386        F: FnOnce() -> String,
387    {
388        match self {
389            Err(err) => die_perror(&msgf(), err),
390            Ok(value) => value,
391        }
392    }
393}
394
395impl<T> ExpectedResult<T> for Option<T> {
396    #[inline]
397    fn expect_result_nb<F>(self, msgf: F) -> Result<T, ExpectationFailed>
398    where
399        F: FnOnce() -> String,
400    {
401        self.ok_or_else(|| ExpectationFailed { message: msgf() })
402    }
403}
404
405impl<T, E> ExpectedResult<T> for Result<T, E>
406where
407    E: Display,
408{
409    #[inline]
410    fn expect_result_nb<F>(self, msgf: F) -> Result<T, ExpectationFailed>
411    where
412        F: FnOnce() -> String,
413    {
414        self.map_err(|err| ExpectationFailed {
415            message: format!("{msg}: {err}", msg = msgf()),
416        })
417    }
418}
419
420impl Expected<Self> for bool {
421    #[inline]
422    fn or_exit<F>(self, msgf: F) -> Self
423    where
424        F: FnOnce() -> String,
425    {
426        if !self {
427            exit(&msgf());
428        }
429        true
430    }
431
432    #[inline]
433    fn or_die<F>(self, msgf: F) -> Self
434    where
435        F: FnOnce() -> String,
436    {
437        if !self {
438            die(&msgf());
439        }
440        true
441    }
442}
443
444impl ExpectedResult<()> for bool {
445    #[inline]
446    fn expect_result_nb<F>(self, msgf: F) -> Result<(), ExpectationFailed>
447    where
448        F: FnOnce() -> String,
449    {
450        if self {
451            Ok(())
452        } else {
453            Err(ExpectationFailed { message: msgf() })
454        }
455    }
456}