Expand description
§Skerry: Super Kool ERRors Yoh
Example:
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.
// 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.
#[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
ErrAis added manually and also exists inside a*expansion, only one variant is generated.
#[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
#[skerry_fn]
pub fn high_level() -> Result<(), e![ErrA, ErrB, ErrC]> {
// ...
}In the cases above the generated enum looks like this
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
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
#[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
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:
#![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>.
| Feature Gate | Effect |
|---|---|
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. |
§Comparison
§Standard Manual Approach
Traditionally, every function and implementation block requires explicit tagging:
#[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.
#![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.
Modules§
Macros§
Attribute Macros§
- skerry
- skerry_
fn - An attribute macro to automate function-specific error handling.
- skerry_
impl - An attribute macro applied to implementation blocks (
impl). - skerry_
mod - A container macro used to define the boundaries of an error-handling module.
- skerry_
trait - An attribute macro applied to trait definitions.