# Elaborate

Wrappers for standard library functions and types to produce more elaborate error messages

## Example

Error message produced by `create_dir_wc`, Elaborate's wrapper for `create_dir`:

```
Error: call failed:
    std::fs::create_dir(
        "/dir",
    )

Caused by:
    Permission denied (os error 13)
```

Compare this to `create_dir`'s error message, which does not mention the call that failed or the path involved:

```
Error: Permission denied (os error 13)
```

<details>

<summary>Rust programs used to produce the above (note that <tt>wc</tt> is mnemonic for "with context")</summary>

```rust
use elaborate::std::fs::create_dir_wc;

fn main() -> anyhow::Result<()> {
    create_dir_wc("/dir")?;
    Ok(())
}
```

```rust
use std::fs::create_dir;

fn main() -> anyhow::Result<()> {
    create_dir("/dir")?;
    Ok(())
}
```

</details>

## Traits and structs

Elaborate provides wrapper traits for standard library traits and structs. The name of each wrapper trait is the name of the wrapped trait or struct with `Context` appended. Like for normal functions, the name of each wrapper trait function is the name of the wrapped function with `_wc` appended.

The following example uses the wrapped versions of the `Write` trait and the `OpenOptions` struct.

```rust
use elaborate::std::{fs::OpenOptionsContext, io::WriteContext};
use std::fs::OpenOptions;

fn main() -> anyhow::Result<()> {
    let mut file = OpenOptions::new()
        .create(true)
        .write(true)
        .open_wc("greeting.txt")?;
    file.write_all_wc(b"Hello, world!")?;
    Ok(())
}
```

Running the above example in a read-only directory produces the following error message:

```
Error: call failed:
    OpenOptions(
        OpenOptions {
            read: false,
            write: true,
            append: false,
            truncate: false,
            create: true,
            create_new: false,
            custom_flags: 0,
            mode: 0o000666,
        },
    ).open(
        "greeting.txt",
    )

Caused by:
    Permission denied (os error 13)
```

## Clippy

This repository provides a [`disallowed_methods` function] to identify functions that could be replaced with wrapped ones. The function returns a `Command` configured to run Clippy's [`disallowed_methods` lint] with a [Clippy configuration] (`clippy.toml`) from this repository.

When running the above function, you should see errors like the following:

```
error: use of a disallowed method `std::fs::create_dir`
 --> src/main.rs:4:5
  |
4 |     create_dir(\"/dir\")?;
  |     ^^^^^^^^^^ help: use: `elaborate::std::fs::create_dir_wc`
  |
  = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_methods
  = note: `-D clippy::disallowed-methods` implied by `-D warnings`
  = help: to override `-D warnings` add `#[allow(clippy::disallowed_methods)]`
```

If you would prefer to run Clippy manually, you can do so with the following command:

```sh
CLIPPY_CONF_DIR=path-to-elaborate-repo/clippy_conf cargo clippy
```

Note that `CLIPPY_CONF_DIR` names the directory containing the `clippy.toml` file, not the `clippy.toml` file itself.

## Alternative approaches considered

**Wrapper structs**, e.g., a struct `File` that wraps a [`std::fs::File`], so that calling a method on the wrapper struct calls the underlying method with [`anyhow::Context::with_context`]. This idea works to a degree but has several problems. Most significantly, the wrapper struct must implement every trait the wrapped struct does. There are many ways a wrapped struct could implement a trait. For example, if a trait provides a default implementation, the wrapped struct could use the default implementation or provide its own. Such facts complicate automatic code generation. Hence, this idea seems untenable.

## Semantic versioning policy

We reserve the right to update the nightly toolchain with which `elaborate` is associated, and to release such changes with only a minor version bump.

Such a policy should not affect users of stable Rust.

However, if you are using nightly Rust and require a specific nightly toolchain, then we recommend using a [tilde requirement] to specify `elaborate` as a dependency.

Example:

```toml
[dependencies]
elaborate = "~1.0"
```

## Credits

Elaborate uses [`public-api`] and [`rustdoc-types`] to generate wrappers, and [`anyhow`] to generate error messages.

[Clippy configuration]: https://doc.rust-lang.org/clippy/configuration.html
[`anyhow::Context::with_context`]: https://docs.rs/anyhow/latest/anyhow/trait.Context.html#tymethod.with_context
[`anyhow`]: https://github.com/dtolnay/anyhow
[`disallowed_methods` function]: https://docs.rs/elaborate/latest/elaborate/fn.disallowed_methods.html
[`disallowed_methods` lint]: https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_methods
[`public-api`]: https://github.com/cargo-public-api/cargo-public-api/tree/main/public-api
[`rustdoc-types`]: https://github.com/aDotInTheVoid/rustdoc-types
[`std::fs::File`]: https://doc.rust-lang.org/std/fs/struct.File.html
[tilde requirement]: https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html#tilde-requirements
