#![no_std]
#![warn(missing_docs)]
extern crate alloc;
use alloc::{
borrow::ToOwned,
string::{String, ToString},
vec,
vec::Vec,
};
pub type Result<T> = core::result::Result<T, Failure>;
#[derive(Clone, PartialEq, Eq, Hash)]
pub struct Failure {
pub message: String,
pub backtrace: Vec<Location>,
}
impl Failure {
#[track_caller]
pub fn new<T: core::fmt::Display>(message: T) -> Self {
Self {
message: message.to_string(),
backtrace: vec![Location::new()],
}
}
}
impl Default for Failure {
#[track_caller]
fn default() -> Self {
Self::new("a failure occurred")
}
}
impl core::fmt::Debug for Failure {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
core::fmt::Display::fmt(self, f)
}
}
impl core::fmt::Display for Failure {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
write!(f, "{}", self.message)?;
writeln!(f)?;
for location in &self.backtrace {
writeln!(f, " at {}:{}", location.file, location.line)?;
}
Ok(())
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct Location {
pub file: String,
pub line: u32,
}
impl Location {
#[track_caller]
pub fn new() -> Self {
let location = core::panic::Location::caller();
Self {
file: location.file().to_owned(),
line: location.line(),
}
}
}
impl Default for Location {
#[track_caller]
fn default() -> Self {
Self::new()
}
}
pub trait OrFail: Sized {
type Value;
type Error;
fn or_fail(self) -> Result<Self::Value>;
fn or_fail_with<F>(self, f: F) -> Result<Self::Value>
where
F: FnOnce(Self::Error) -> String;
}
impl OrFail for bool {
type Value = ();
type Error = ();
#[track_caller]
fn or_fail(self) -> Result<Self::Value> {
if self {
Ok(())
} else {
Err(Failure::new("expected `true` but got `false`"))
}
}
#[track_caller]
fn or_fail_with<F>(self, f: F) -> Result<Self::Value>
where
F: FnOnce(Self::Error) -> String,
{
if self {
Ok(())
} else {
Err(Failure::new(f(())))
}
}
}
impl<T> OrFail for Option<T> {
type Value = T;
type Error = ();
#[track_caller]
fn or_fail(self) -> Result<Self::Value> {
if let Some(value) = self {
Ok(value)
} else {
Err(Failure::new("expected `Some(_)` but got `None`"))
}
}
#[track_caller]
fn or_fail_with<F>(self, f: F) -> Result<Self::Value>
where
F: FnOnce(Self::Error) -> String,
{
if let Some(value) = self {
Ok(value)
} else {
Err(Failure::new(f(())))
}
}
}
impl<T, E: core::error::Error> OrFail for core::result::Result<T, E> {
type Value = T;
type Error = E;
#[track_caller]
fn or_fail(self) -> Result<Self::Value> {
match self {
Ok(t) => Ok(t),
Err(e) => Err(Failure::new(e)),
}
}
#[track_caller]
fn or_fail_with<F>(self, f: F) -> Result<Self::Value>
where
F: FnOnce(Self::Error) -> String,
{
match self {
Ok(t) => Ok(t),
Err(e) => Err(Failure::new(f(e))),
}
}
}
impl<T> OrFail for Result<T> {
type Value = T;
type Error = String;
#[track_caller]
fn or_fail(self) -> Result<Self::Value> {
match self {
Ok(value) => Ok(value),
Err(mut failure) => {
failure.backtrace.push(Location::new());
Err(Failure {
message: failure.message,
backtrace: failure.backtrace,
})
}
}
}
#[track_caller]
fn or_fail_with<F>(self, f: F) -> Result<Self::Value>
where
F: FnOnce(Self::Error) -> String,
{
match self {
Ok(value) => Ok(value),
Err(mut failure) => {
failure.backtrace.push(Location::new());
let message = f(failure.message);
Err(Failure {
message,
backtrace: failure.backtrace,
})
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_works() {
assert!((true.or_fail() as Result<_>).is_ok());
assert!((false.or_fail() as Result<_>).is_err());
let failure: Failure = false.or_fail().err().unwrap();
assert_eq!(failure.backtrace.len(), 1);
let failure: Failure = false.or_fail().or_fail().err().unwrap();
assert_eq!(failure.backtrace.len(), 2);
}
}