# 🎈 thisctx
A small crate works with [thiserror](https://crates.io/crates/thiserror) to
create errors with contexts, heavily inspired by
[snafu](https://crates.io/crates/snafu).
## ✍️ Examples
```rust
#[derive(Debug, Error, WithContext)]
pub enum Error {
#[error("I/O failed at '{1}'")]
Io(#[source] std::io::Error, PathBuf),
#[error(transparent)]
ParseInt(std::num::ParseIntError),
}
fn read_file(path: &Path) -> Result<String, Error> {
std::fs::read_to_string(path).context(Io(path))
}
```
## ⚙️ Attributes
You can use the `#[thisctx]` attribute with the following options to customize
the expanded code:
| `attr` | `TokenStream[]` | ✔ | ✔ | ✔ | ✔ |
| `generic` | `bool` | ✔ | ✔ | ✔ | ✔ |
| `into` | `Type[]` | ✔ | ✔ | ✔ | |
| `module` | `bool \| Ident` | | ✔ | | |
| `skip` | `Ident` | ✔ | ✔ | ✔ | |
| `suffix` | `bool \| Ident` | ✔ | ✔ | ✔ | |
| `unit` | `bool` | ✔ | ✔ | ✔ | |
| `visibility` | `Visibility` | ✔ | ✔ | ✔ | ✔ |
The `#[source]` and `#[error]` attributes defined in `thiserror` will also be
checked to determine the source error type.
### Option arguments
`#[thisctx]` supports two syntaxes for passing arguments to an option:
- Put tokens directly in the parentheses, e.g. `#[thisctx(visibility(pub))]`
- Use a string literal, e.g. `#[thisctx(visibility = "pub")]`, this is useful in
older versions of `rustc` that don't support arbitrary tokens in non-macro
attributes.
An option of type `T[]` can occur multiple times in the same node, while other
types will lead an error.
### Boolean options
You can omit the `true` value in boolean options, e.g. `#[thisctx(skip)]` is
equal to `#[thisctx(skip(true))]`.
Reversed boolean options starts with `no_` can also be used as a shortcut to
pass `false`, e.g. `#[thisctx(no_skip)]` is equal to `#[thisctx(skip(false))]`.
### Inherited options
An inherited option uses the value of its parent node if no value is provided,
for example:
```rust
#[derive(WithContext)]
#[thisctx(skip)]
enum Error {
// This variant will be ignored since `skip=true` is inherited.
Io(#[source] std::io::Error),
// This variant will be processed.
#[thisctx(no_skip)]
ParseInt(#[source] std::num::ParseIntError),
}
```
An option of type `T[]` will concatenate arguments from its ancestors instead of
overriding them.
```rust
#[derive(WithContext)]
#[thisctx(attr(derive(Debug)))]
enum Error {
#[thisctx(attr(derive(Clone, Copy)))]
Io(#[source] std::io::Error),
ParseInt(#[source] std::num::ParseIntError),
}
```
Expanded example:
```rust
// The order of attributes (and other options) is guaranteed by the order of
// inheritance.
// Attributes from the child node.
#[derive(Clone, Copy)]
// Attributes from the parent node.
#[derive(Debug)]
struct Io;
#[derive(Debug)]
struct ParseInt;
```
### `source`
If a field has the `#[source]` attribute or is named `source`, the type of this
field will be assigned to `IntoError::Source` and won't appear in the generated
context types.
```rust
#[derive(WithContext)]
struct Error(#[source] std::io::Error, PathBuf);
```
Expanded example:
```rust
struct ErrorContext<T1 = PathBuf>(T1);
impl<T1> IntoError<Error> for ErrorContext<T1>
where
T1: Into<PathBuf>,
{
type Source = std::io::Error;
fn into_error(self, source: Self::Source) -> Error {
Error(source, self.0.into())
}
}
```
### `error`
If a variant is transparent (which has `#[error(transparent)]`), the first field
(which should also be the only field) will be considered as the source field.
### `thisctx.attr`
An option used to add extra attributes to a generated node.
```rust
#[derive(WithContext)]
#[thisctx(attr(derive(Debug)))]
struct Error {
reason: String,
}
```
Expanded example:
```rust
#[derive(Debug)]
struct ErrorContext<T1 = String> {
reason: T1,
}
```
`thisctx` allows you to add some common attributes without `attr(...)`,
including:
- `cfg`
- `cfg_attr`
- `derive`
- `doc`
This means the above example can also be written as:
```rust
#[derive(WithContext)]
#[thisctx(derive(Debug))]
struct Error {
reason: String,
}
```
### `thisctx.generic`
An option to disable generics of a generated node.
```rust
#[derive(WithContext)]
struct Error {
reason: String,
#[thisctx(no_generic)]
path: PathBuf,
}
```
Expanded example:
```rust
struct ErrorContext<T1 = String> {
reason: T1,
path: PathBuf,
}
```
The generics provide a convenient way to construct context types, for example:
```rust
let _: Error = ErrorContext {
// You can use &str directly because String implements From<&str>,
reason: "anyhow",
// whereas without generics you have to convert the data to PathBuf manually.
path: "/some/path".into(),
}.build();
```
### `thisctx.into`
An option for converting generated types to a remote error type.
```rust
// Probably an error defined in another crate.
enum RemoteError {
Custom(String),
}
// From<T> is required by #[thisctx(into)]
impl From<MyError> for RemoteError {
fn from(e: MyError) -> Self {
Self::Custom(e.0)
}
}
#[derive(WithContext)]
#[thisctx(into(RemoteError))]
struct MyError(String);
let _: MyError = MyErrorContext("anyhow").build();
// It's possible to construct a remote error from the local context type.
let _: RemoteError = MyErrorContext("anyhow").build();
```
### `thisctx.module`
This option allows you put all generated context types into a single module.
```rust
#[derive(WithContext)]
#[thisctx(module(context))]
pub enum Error {
Io(#[source] std::io::Error),
ParseInt(#[source] std::num::ParseIntError),
}
```
Expanded example:
```rust
pub mod context {
pub struct Io;
pub struct ParseInt;
}
```
> You can also set this option to `true` to use the snake case of the container
> name as the module name, e.g. `#[thisctx(module)]` on `enum MyError` is equal
> to `#[thisctx(module(my_error))]`.
### `thisctx.skip`
This option is used to skip generating context types for the specified variant.
```rust
#[derive(WithContext)]
enum Error {
#[thisctx(skip)]
Io(#[source] std::io::Error),
ParseInt(#[source] std::num::ParseIntError),
}
```
Expanded example:
```rust
struct ParseInt;
```
### `thisctx.suffix`
An option to add a suffix to the names of the generated context types.
By default, only `struct`s will be added the builtin suffix `Context` since the
generated type without a suffix will confict with the error type.
```rust
#[derive(WithContext)]
#[thisctx(suffix(Error))]
enum Error {
Io(#[source] std::io::Error),
ParseInt(#[source] std::num::ParseIntError),
}
```
Expanded example:
```rust
struct IoError;
struct ParseIntError;
```
> The value `true` means to use the default suffix `Context` and the value
> `false` will remove the suffix from the generated type.
### `thisctx.unit`
In Rust, the parentheses are required to construct a tuple struct even if it's
empty. `thisctx` will convert an empty struct to a unit struct by default. This
allows you use the struct name to create a new context without having to add
parentheses each time and can be disabled by passing `#[thisctx(no_unit)]`.
```rust
#[derive(WithContext)]
enum Error {
#[thisctx(no_unit)]
Io(#[source] std::io::Error),
ParseInt(#[source] std::num::ParseIntError),
}
```
Expanded example:
```rust
struct IoError();
struct ParseIntError;
```
### `thisctx.visibility`
This option is used to change the visibility of the generated types and fields
and can be written in shorthand as `#[pub(...)]`.
```rust
#[derive(WithContext)]
#[thisctx(pub(crate))]
pub enum Error {
Io(#[source] std::io::Error),
ParseInt(#[source] std::num::ParseIntError),
}
```
Expanded example:
```rust
pub(crate) struct IoError;
pub(crate) struct ParseIntError;
```
## 📝 Todo
- [x] ~~Switch to Rust 2021.~~
- [x] MSRV v1.33
- [x] Use derive macro instead.
- [x] Add attributes to context types.
- [x] Support transparent error.
- [x] Support generics.
- [x] Simplify the derive implementation.
- [x] More documentation.
- [ ] More tests.
## 🚩 Minimal suppoted Rust version
All tests under `tests/*` passed with `rustc v1.33`, previous versions may not
compile.
## ⚖️ License
Licensed under either of
- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or
<http://www.apache.org/licenses/LICENSE-2.0>)
- MIT license ([LICENSE-MIT](LICENSE-MIT) or
<http://opensource.org/licenses/MIT>)
at your option.