erreur 0.2.1

A tiny crate that facilitates error handling, including tracing line numbers, customizing and propagating error messages.
Documentation
Other languages: [中文](./README.zh.md), [En français](./README.fr.md).

# Quick Start

After reading through this article, you will master the usage of:
* `Resultat<T>` as return type
* `assert_throw!(...)`
* `throw!(...)`
* `.catch()?` and `.catch_()?`
* `.ifnone()?` and `.ifnone_()?` 

## 1. Example of returning `Resultat<T>`

```rust 
use erreur::*;

pub fn rand_even() -> Resultat<u64> {
    // ... function body here ...
    Ok(1)
}
```

NOTE: If you are writing your own fail-able function, let it return `Resultat<T>`. 

## 2. Example of `assert_throw!(...)`

```rust
use erreur::*;

fn rand_even(rng: &mut impl RngExt) -> Resultat<u64> {
    let n: u64 = rng.random_range(1..=1000_0000);
    assert_throw!(
        // [required] boolean expression
        n % 2 == 0,
        // [optional] title          
        "UnluckyException",     
        // [optional] error message      
        format!("{} is not even", n)  
    );
    Ok(n)
}
```

NOTE: if **exactly one** optional arg is given, the arg is treated as **error message**, and the title is automatically set to `"AssertionFailedException"`.

## 3. Example of `throw!(...)`

```rust
fn rand_odd(rng: &mut impl RngExt) -> Resultat<u64> {
    let n: u64 = rng.random_range(1..=1000_0000);
    if n % 2 == 1 {
        return Ok(n);
    } else {
        throw!(
            // [required] title.
            "UnluckyException",
            // [required] error message.
            format!("{} is not odd", n)
        );

        // throw!(); // Lazy variant
        // throw!("UnknownException", ""); // equivalent
    }
}
```

## 4. Example of `.catch(...)?` and `.catch_()?`

### 4.1. Full catch

This is extremely useful when the underlying error message is confusing. The programmer is responsible for customizing helpful error message.

```rust
use erreur::*;
use std::fs::File;

fn main() -> Resultat<()> {
    // `File::open` will show the following message on this path
    // -- "No such file or directory (os error 2)".
    // When you see this message in real business, 
    // --  you have no idea which file is missing.
    let path = "/impossible/path/!@#$%^&*()_+.file";

    // `catch` the `Result` returned by `open`,
    // write helpful message in `catch`.
    let _file = File::open(path).catch("CannotOpenFile", path)?;

    Ok(())
}
```

### 4.2. Lazy catch

If the underlying error message is helpful enough, use lazy catch to track the call stack and propagate the error message.

```rust
use erreur::*;
use rand::{Rng, RngExt};

fn main() -> Resultat<()> {
    let mut rng = rand::rng();

    let even = rand_even(&mut rng).catch_()?;
    println!("{}", even);

    let odd = rand_odd(&mut rng).catch_()?;
    println!("{}", odd);

    Ok(())
}
```

## 5. Example of `.ifnone(...)?` and `.ifnone_()?`

If an `Option::None` stucks your business, call `.ifnone(...)?` or `.ifnone_()?` to throw an error.

```rust
use std::collections::HashMap;

use erreur::*;
use rand::Rng;

fn main() -> Resultat<()> {
    let mut rng = rand::rng();

    let zoo = init_dict();
    let dice = rng.random_range(1..=6);

    let animal = zoo
        .get(&dice)
        .ifnone(
            "UnluckyException",
            format!("dice = {}", dice),
        )?;
    // there's also a lazy variant: `.ifnone_()?`
    println!("{}", animal);

    Ok(())
}

fn init_dict() -> HashMap<i32, String> {
    let mut dict = HashMap::new();
    dict.insert(1, "bear".to_string());
    dict.insert(3, "kangaroo".to_string());
    dict.insert(5, "cockatoo".to_string());
    dict
}
```