## Skerry: Super Kool ERRors Yoh
Example:
```rust
use skerry::*;
// There can be only one #[sherry_mod] in your project
#[skerry_mod]
pub mod errors {
pub struct DatabaseError;
pub struct AuthError;
pub struct ValidationError;
pub struct InvalidParse;
#[from]
pub struct LibError(ErrorFromLib);
}
// Generates a CheckAuthError enum automatically
#[skerry_fn]
fn check_auth() -> Result<(), e![AuthError]> {
Err(CheckAuthError::AuthError(AuthError))
}
struct Controller;
#[skerry_impl(prefix(Controller))] // This allows #[skerry_fn] to run on impl blocks
impl Controller {
// Use '*' to expand and bubble up sub-errors seamlessly.
#[skerry_fn]
pub fn run() -> Result<(), e![LibError, *CheckAuthError]> {
// AuthError is pulled in from check_auth via '*CheckAuthError'
check_auth()?;
// Automatically bubble up library errors as long as an
// error from `#[skerry_mod]` implements `From` for it.
lib_fn_that_returns_error()?;
Ok(())
}
}
#[skerry_trait]
trait ToJson {
#[skerry_fn]
fn to_json(&self) -> Result<(), e![InvalidParse]>;
}
#[skerry_impl]
impl ToJson for Controller {
// Whenever you do not want to generate a new error just don't
// use e![], this will instead reuse an existing error
#[skerry_fn]
fn to_json(&self) -> Result<(), ToJsonError> {
Ok(())
}
}
// Manually define composite errors like this
define_error!(ManualDefine, [ValidationError, *ToJsonError]);
```
### Core Workflow
- Define all possible error structs in a `#[skerry_mod]`.
- Mark functions with `#[skerry_fn]`.
- Use the `*` operator to bubble up errors from sub-functions without manually mapping variants.
---
### The Error Module
Every project needs one module (usually `errors.rs`) that acts as the source of truth.
```rust
// Recommended to be pub for easier macro expansions
pub use skerry::*;
#[skerry_mod]
mod errors {
pub struct ErrA;
pub struct ErrB;
pub struct ErrC;
pub struct DatabaseErr;
#[from]
pub struct OuterLibError(LibError);
}
```
You can also anotate with `#[from]` to automatically add conversions from the inner type.
This is only valid for tuple structs with a single element.
*Note: When using errors in any other file, import them via `crate::errors::*;` instead
of individual imports to ensure the macros can resolve the paths correctly.*
---
### Function-Specific Enums
By using `#[skerry_fn]`, you define a return type using a tuple of error structs.
Skerry transforms this into a unique enum named `{FunctionName}Error`.
```rust
#[skerry_fn]
pub fn low_level() -> Result<(), e![ErrA, ErrB]> {
// Generates LowLevelError { ErrA(ErrA), ErrB(ErrB) }
Err(LowLevelError::ErrA(ErrA))
// You can also type Err(ErrA.into())
}
```
---
### The Asterisk (`*`) Expansion
When you put `*OtherFnError` in your return array it pulls all
variants from `OtherFnError` into your current function's list.
* **Deduplication**: Variants are deduplicated automatically. If `ErrA` is added manually
and also exists inside a `*` expansion, only one variant is generated.
```rust
#[skerry_fn]
pub fn high_level() -> Result<(), e![ErrC, *LowLevelError]> {
// Sees ErrC -> Adds variant
// Sees *LowLevelError -> Inspects LowLevelError, finds (ErrA, ErrB)
// Final HighLevelError contains variants: ErrA, ErrB, ErrC
low_level()?; // Bubbles up automatically
Ok(())
}
```
The syntax below has the exact same effects, `*LowLevelError` is nothing more than syntatic sugar
```rust
#[skerry_fn]
pub fn high_level() -> Result<(), e![ErrA, ErrB, ErrC]> {
// ...
}
```
In the cases above the generated enum looks like this
```rust
pub enum HighLevelError {
ErrA(ErrA),
ErrB(ErrB),
ErrC(ErrC),
}
```
### Using Skerry inside Impl Blocks
Skerry provides the `#[skerry_impl]` attribute to handle methods within `impl` blocks.
This attribute coordinates with `#[skerry_fn]` to split the generated code
so error enums are generated outside the `impl` block.
#### Example
```rust
pub struct Database;
#[skerry_impl(prefix(Database))] // Optional prefix for functions inside impl block
impl Database {
#[skerry_fn]
pub fn connect(&self) -> Result<(), e![*RemoteCallError]> {
remote_call()?;
Ok(())
}
}
fn main() {
let db = Database;
let result: Result<(), DatabaseConnectError> = db.connect();
assert!(result.is_ok());
}
```
### Using Skerry inside Trait Blocks
Skerry provides the `#[skerry_trait]` attribute to handle methods within `trait` blocks.
This attribute coordinates with `#[skerry_fn]` to split the generated code
so error enums are generated outside the `trait` block.
#### Example
```rust
#[skerry_trait(prefix(ToJson))] // Optional prefix for functions inside trait block
trait ToJson {
#[skerry_fn]
fn parse(&self) -> Result<(), e![ParseFailed]>;
}
```
## Manual Error Definitions
Manually define composite errors using the `define_error!` macro.
This allows you to skip needing a function to define errors.
#### Example
```rust
define_error!(ManualDefine, [ErrorA, ErrorB]);
#[skerry_fn]
fn my_func_with_custom_error() -> Result<(), ManualDefine> {
Ok(())
}
```
## Custom Result Feature
The `custom_result` feature implements a specialized result type and leverages the
unstable features to enable even more automation.
### Nightly Features Required
To use the full suite of automation provided by this feature, you must enable the
following nightly features in your crate root:
```rust
#![feature(try_trait_v2)]
#![feature(custom_inner_attributes)]
#![feature(proc_macro_hygiene)]
```
### Overview
Enabling `custom_result` changes the behavior of the `?` operator to support
automatic conversion into `GlobalErrors<I>`.
| `try_trait_v2` | Allows using `?` with custom `Result` types; removes the strict requirement for `#[skerry_fn]` on standard functions. |
| `custom_inner_attributes`/`proc_macro_hygiene` | Enables the use of `#![skerry]` at the top of a file to annotate all contents automatically. |
### Error Declaration
Use `#![skerry_mod]` to define concise error sets. The `#[from]` attribute allows
automatic wrapping of external library errors.
```rust
//! errors.rs
#![skerry_mod]
pub struct ErrA;
pub struct ErrB;
#[from]
pub struct Outer(OuterErrorFromLib);
```
### Comparison
#### Standard Manual Approach
Traditionally, every function and implementation block requires explicit tagging:
```rust
#[skerry_fn]
fn check_auth() -> Result<(), e![AuthError]> {
Err(CheckAuthError::AuthError(AuthError))
}
struct Controller;
#[skerry_impl(prefix(Controller))]
impl Controller {
#[skerry_fn]
pub fn run() -> Result<(), e![LibError, *CheckAuthError]> {
check_auth()?;
lib_fn_that_returns_error()?;
Ok(())
}
}
#[skerry_trait(prefix(ToJson))]
trait ToJson {
#[skerry_fn]
fn to_json(&self) -> Result<(), e![InvalidParse]>;
}
impl ToJson for Controller {
#[skerry_fn]
fn to_json(&self) -> Result<(), ToJsonError> {
Ok(())
}
}
```
#### Automated Approach with `custom_result`
By adding `#![skerry]` to the top of your module, the boilerplate is handled
automatically.
```rust
#![skerry]
fn check_auth() -> Result<(), e![AuthError]> {
Err(CheckAuthError::AuthError(AuthError))
}
struct Controller;
impl Controller {
pub fn run() -> Result<(), e![LibError, *CheckAuthError]> {
check_auth()?;
lib_fn_that_returns_error()?;
Ok(())
}
}
trait ToJson {
fn to_json(&self) -> Result<(), e![InvalidParse]>;
}
impl ToJson for Controller {
fn to_json(&self) -> Result<(), ToJsonError> {
Ok(())
}
}
```
---
### Compile-Time Safety
Skerry uses a custom trait system (`MissingConvert`) to verify error bounds at
compile-time. If you try to use `?` on a function whose errors are not represented
in your current return tuple, the compiler will refuse to build.
License: MIT