1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
//! Utilities for reporting fatal errors and exiting with an error code.
//!
//! The behavior in this crate is different than the one in [`panic!`](::std::panic!)-based exits,
//! in that the ones here are suited for display to end-users, i.e. no "thread `main` panicked at", no backtrace mentions, etc.
//!
//! # Usage
//! For unwrapping [`Result`](Result)s:
//! - Use [`expect`](expect) / [`expect_fatal`](UnwrapExt::expect_fatal) to report the error with context.
//! - Use [`unwrap_message!`](unwrap_message) to [`expect`](expect) with formatting.
//! - Use [`unwrap_format!`](unwrap_format) to have more control over the message's format.
//! - Use [`unwrap`](unwrap) / [`unwrap_fatal`](UnwrapExt::unwrap_fatal) to report the error when context is provided/obvious.
//!
//! For aborting:
//! - Use [`error!`](error) to report context + error.
//! - Use [`fatal!`](fatal) when [`error!`](error)'s prefix is unwelcome.
//!
//! # (Pseudo-)Example:
//! ```ignore
//! use fatal::UnwrapExt;
//!
//! const DB_CONSTR_VAR: &str = "DB_CONNECTION_STRING";
//!
//! fn main() {
//!     println!("Connecting..");
//!     let constr: String = fatal::unwrap_message!(std::env::var(DB_CONSTR_VAR), "failed to read the `{}` environment variable", DB_CONSTR_VAR);
//!     // when doesn't exist, will print: "Error: failed to read the `DB_CONNECTION_STRING` environment variable (environment variable not found)"
//!     let db: Database = Database::connect(&constr).expect_fatal("failed to connect to database");
//!     // would also include the actual error as above.
//!
//!     println!("Querying total users..");
//!     println!("Total users: {}", db.query_total_users().unwrap_fatal());
//! }
//! ```

use std::fmt::Display;

#[macro_export]
/// Prints to standard-error and exits with an error-code. Returns [`!`](https://doc.rust-lang.org/std/primitive.never.html).
///
/// Equivalent to [`eprintln!`](::std::eprintln) followed by [`process::exit`](::std::process::exit).
macro_rules! fatal {
  () => { ::std::process::exit(1) };
  ($($arg:tt)*) => {
    {
      ::std::eprintln!($($arg)*);
      $crate::fatal!()
     }
  };
}

/// Yields the error prefix string.
///
/// This is a macro to minimize code generation (compared to a `println!("{}", ERROR_PREFIX_CONST)`).
macro_rules! get_error_prefix { () => {"Error: "} }

#[doc(hidden)]
/// Write the error prefix for the [error!](error) macro.
///
/// This function is internal.
pub fn internal_write_error_prefix() {
  #[cfg(feature = "color")]
  if !internal_write_red_error_prefix() { eprint!(get_error_prefix!()); }

  #[cfg(not(feature = "color"))]
  eprint!(get_error_prefix!());
}

#[doc(hidden)]
#[cfg(feature = "color")]
/// Prints the error prefix for the [error!](error) macro in red.
///
/// Returns whether the function printed, regardless if it succeeded or not.
/// In other words, if false, we should retry but fallback to normal printing.
fn internal_write_red_error_prefix() -> bool {
  use std::io::Write;
  let mut stderr = termcolor::StandardStream::stderr(termcolor::ColorChoice::Auto);
  if termcolor::WriteColor::set_color(&mut stderr, termcolor::ColorSpec::new().set_fg(Some(termcolor::Color::Red))).is_err() { return false }
  let did_write = write!(&mut stderr, get_error_prefix!()).is_ok();
  termcolor::WriteColor::reset(&mut stderr)
    .ok(); // ignore any potential error, we passed the point of no return.
  did_write
}

#[macro_export]
/// Prints an error message to standard-error and exits with an error code.
///
/// Equivalent to [`fatal!`](fatal), but prefixes the message (when present) with “Error: ”.
/// If the `color` flag is set, will attempt to color the prefix in red.
///
/// # User Experience
/// The message you write in the arguments is in the middle of a sentence, so you may or may not want to capitalize the beginning (unless it's a proper-noun, of course).
/// Grammatically, either way is valid so it's just a matter of style.
///
/// E.g.
/// ```ignore
/// error!("bad input") // "Error: bad input"
/// error!("Bad input") // "Error: Bad input"
/// ```
macro_rules! error {
  () => { $crate::fatal!() };
  ($($arg:tt)*) => {
    {
      $crate::internal_write_error_prefix();
      $crate::fatal!($($arg)*);
    }
  };
}

/// Unwraps a result or reports its error and exits.
///
/// The error is reported with [`error!`](error).
///
/// See [`UnwrapExt`](UnwrapExt) for an extension trait version.
///
/// # User Experience
/// Be mindful to not be too lazy because error values usually don't have the context to report even remotely acceptable messages.
/// If context wasn't provided or isn't otherwise obvious, you should probably use [`expect!`](expect).
pub fn unwrap<T,E: Display>(result: Result<T,E>) -> T {
  result.unwrap_or_else(|e| error!("{}", e))
}

/// Unwraps a result or reports the given message with the error and exits.
///
/// The error is reported with [`error!`](error).
///
/// See [`UnwrapExt`](UnwrapExt) for an extension trait version.
pub fn expect<T,E: Display>(result: Result<T,E>, message: impl Display) -> T {
  result.unwrap_or_else(|e| error!("{} ({})", message, e))
}

/// An extension trait for [`unwrap`](unwrap).
pub trait UnwrapExt {
  type T;

  /// An extension synonym for [`unwrap`](unwrap).
  fn unwrap_fatal(self) -> Self::T;

  /// An extension synonym for [`expect`](expect).
  fn expect_fatal(self, message: impl Display) -> Self::T;
}

impl<T,E: Display> UnwrapExt for Result<T,E> {
  type T = T;
  fn unwrap_fatal(self) -> Self::T { unwrap(self) }
  fn expect_fatal(self, message: impl Display) -> Self::T { expect(self, message) }
}

#[macro_export]
/// Unwraps the result or formats an error message and exits.
///
/// This is like [`unwrap`](unwrap) but enables formatting. The error message is in the [named parameter](https://doc.rust-lang.org/std/fmt/index.html#named-parameters)
/// `error` (i.e. `{error}` will show it).
///
/// The first argument should be a [Result](Result) such that its error implements either [`Debug`](std::fmt::Debug) or [`Display`](std::fmt::Display).
/// The rest of the arguments are as in [format!](::std::format).
///
/// The `{error}` named parameter must be used!
macro_rules! unwrap_format {
  ($result:expr, $msg:tt) => {
    $result.unwrap_or_else(|e| $crate::error!($msg, error=e))
  };
  ($result:expr, $fmt:tt, $($param:tt)*) => {
    $result.unwrap_or_else(|e| $crate::error!($fmt, $($param)*, error=e))
  };
}

/// Unwraps the result or reports the error with the error description and exits.
///
/// This is like [`unwrap_format!`](unwrap_format) but always appends the error message at the end.
#[macro_export]
macro_rules! unwrap_message {
  ($result:expr, $msg:tt) => {
    $result.unwrap_or_else(|e| $crate::error!(::std::concat!($msg, " ({error})"), error=e))
  };
  ($result:expr, $msg:tt, $($param:tt)*) => {
    $result.unwrap_or_else(|e| $crate::error!(::std::concat!($msg, " ({error})"), $($param)*, error=e))
  };
}

#[cfg(test)]
mod test {
  use super::*;

  #[allow(unreachable_code, dead_code)]
  // Just tests that expansions produce code that can even compile.
  fn test_expansions_compiles() {
    let r = Ok::<(),bool>(());
    let r = &r;

    unwrap_format!(r, "Error {error}");
    unwrap_format!(r, "Err{} {error}", "or");

    unwrap_message!(r, "Error");
    unwrap_message!(r, "Error {error}");
    unwrap_message!(r, "Err{} {error}", "or");

    error!();
    error!("Error");
    error!("Err{}", "or");

    fatal!();
    fatal!("Error");
    fatal!("Err{}", "or");
  }
}