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}