8 releases (stable)
Uses new Rust 2024
| 1.1.3 | Mar 7, 2026 |
|---|---|
| 1.1.1 | Feb 1, 2026 |
| 1.1.0 | Jan 31, 2026 |
| 1.0.2 | Nov 17, 2025 |
| 0.0.0 | Sep 13, 2025 |
#173 in Procedural macros
Used in 4 crates
77KB
1K
SLoC
Easy Macros
Automatic error context for any Rust project + powerful procedural macro utilities.
Table of Contents
Quick Start
# Only automatic error context
[dependencies]
easy-macros = { version = "...", features = ["general"] }
# All tools for proc-macro development
[dependencies]
easy-macros = { version = "...", features = ["full"] }
Features
1. Automatic Error Context - Works in Any Project
Feature flag: always-context (included in general and full)
Add .with_context() to all ? operators automatically:
let user_id = 123u64;
let user = find_user(user_id)?; // Auto-context with user_id
let profile = load_profile(&user)?; // Auto-context
let data = fetch_data(profile.id)?; // Auto-context
Ok(())
Control attributes: #[no_context], #[context(display)], #[context(ignore)]
2. Attribute Pattern Matching
Feature flag: attributes (included in full)
Extract values from attributes using __unknown__ placeholder:
let input: syn::ItemStruct = parse_quote! {
#[derive(Debug)]
#[api_version(v2)]
struct ApiRoutes {
#[route(GET, "/users")]
#[deprecated]
list_users: String,
#[route(POST, "/users")]
create_user: String,
#[route(GET, "/users/{id}")]
get_user: String,
unrouted_field: String,
}
};
// 1. has_attributes! - Check if struct has required attributes
let is_valid_api = has_attributes!(input, #[derive(Debug)] #[api_version(v2)]);
assert!(is_valid_api);
// 2. get_attributes! - Extract API version from struct
let versions: Vec<TokenStream> = get_attributes!(input, #[api_version(__unknown__)]);
assert_eq!(versions[0].to_string(), "v2");
// 3. fields_with_attributes! - Filter only deprecated route fields
let deprecated: Vec<(usize, &Field)> =
fields_with_attributes!(&input, #[route(GET, "/users")] #[deprecated]).collect();
assert_eq!(deprecated.len(), 1);
// 4. fields_get_attributes! - Extract HTTP methods from all routed fields
let routes: Vec<(usize, &Field, Vec<TokenStream>)> =
fields_get_attributes!(&input, #[route(__unknown__, "/users")]);
assert_eq!(routes.len(), 2); // list_users and create_user
assert_eq!(routes[0].2[0].to_string(), "GET");
assert_eq!(routes[1].2[0].to_string(), "POST");
Ok(())
3. Exhaustive AST Traversal
Feature flag: all-syntax-cases (included in full)
Generate recursive handlers for all syn types:
all_syntax_cases! {
setup => {
generated_fn_prefix: "process",
additional_input_type: &mut Context,
}
default_cases => {
// Called for matching types across entire AST
fn handle_expr(_expr: &mut syn::Expr, _ctx: &mut Context);
#[after_system] // Run after children processed
fn finalize(_item: &mut syn::Item, _ctx: &mut Context);
// Handle multiple syn types together (e.g., attributes + generics)
fn check_attrs_and_generics(
_attrs: &mut Vec<syn::Attribute>,
_generics: &mut syn::Generics,
_ctx: &mut Context
);
}
special_cases => {
// Override for specific variants
fn handle_call(_call: &mut syn::ExprCall, _ctx: &mut Context);
}
}
// Function implementations for the handlers
fn handle_expr(_expr: &mut syn::Expr, _ctx: &mut Context) {
// Process expressions
}
fn finalize(_item: &mut syn::Item, _ctx: &mut Context) {
// Finalize items after processing children
}
fn check_attrs_and_generics(
_attrs: &mut Vec<syn::Attribute>,
_generics: &mut syn::Generics,
_ctx: &mut Context,
) {
// Check attributes and generics together
}
fn handle_call(_call: &mut syn::ExprCall, _ctx: &mut Context) {
// Handle function calls specially
}
Smart unwrapping of Box<T>, Vec<T>, Punctuated<T, _>. Generates handlers for Item, Expr, Stmt, Pat, Type, and more.
4. Helper Utilities
Feature flags: Individual helpers or full for all
use std::fs;
// Manual error context with file/line info
// Feature: `context` (included in `general` and `full`)
fn load_config() -> anyhow::Result<String> {
fs::read_to_string("file.txt").with_context(context!("Loading config"))
}
// Token stream builder
// Feature: `tokens-builder` (included in `full`)
let mut tokens = TokensBuilder::default();
tokens.add(quote! { println!("Hello"); });
tokens.braced(); // Wrap in { }
let stream = tokens.finalize();
assert!(!stream.is_empty());
// Generate indexed names: field0, field1, field2
// Feature: `indexed-name` (included in `full`)
let names = indexed_name(syn::parse_quote!(field), 3);
assert_eq!(names.len(), 3);
// Find crates (handles renames)
// Feature: `find-crate` (included in `full`)
if let Some(path) = find_crate("quote", quote!()) {
assert!(!path.to_string().is_empty());
}
// Returns first found crate or None
let async_rt = find_crate_list(&[("tokio", quote!()), ("async-std", quote!())]);
// Parse that returns Ok(...) with compile_error! on failure
// Feature: `parse-macro-input` (included in `full`)
let parsed = parse_macro_input!(input as syn::DeriveInput);
5. Result Type for Proc Macros
Feature flag: anyhow-result (included in full)
Use anyhow::Result<TokenStream> in proc macros:
use easy_macros::anyhow_result;
#[proc_macro_derive(MyTrait)]
#[anyhow_result]
fn derive_my_trait(input: TokenStream) -> anyhow::Result<TokenStream> {
let parsed: syn::DeriveInput = syn::parse(input)?;
anyhow::ensure!(!parsed.fields.is_empty(), "Struct must have fields");
Ok(quote! { /* generated */ }.into())
}
// Errors convert to compile_error! automatically
6. Add Code - Make (Docify) Examples minimal
Feature flag: add-code (included in full)
Use #[add_code] to inject setup/teardown or assertions around a function while keeping
docify-generated examples clean and minimal.
use easy_macros::add_code;
#[add_code(before = { let _guard = setup(); })]
fn example() {
// This stays minimal for docify examples.
do_work();
}
// The code inside the braces is inserted without the braces.
#[add_code(after = {
assert!(is_valid());
Ok(())
})]
fn with_return() -> Result<(), Error> {
do_more_work();
}
#[add_code(before = { let _guard = setup(); }, after = { teardown(); })]
fn both() {
do_another_work();
}
Feature Flags
Feature Groups
-
general- Automatic error context for any project- Includes:
always-context,context - Use when you only need automatic error context, not proc-macro development tools
- Includes:
-
full- Complete toolkit for proc-macro development- Includes:
all-syntax-cases,always-context,attributes,anyhow-result,add-code, and all helpers - Use when building procedural macros or need the full feature set
- Includes:
-
build- Build-time macro that auto-adds#[always_context]to all functions returninganyhow::Result- Standalone feature, not included in
generalorfull - Add to
[build-dependencies]and configure viabuild.rs
- Standalone feature, not included in
Individual Features
Core Proc-Macro Tools:
all-syntax-cases- Exhaustive AST traversal and handler generationalways-context-#[always_context]attribute for automatic error contextattributes- Attribute pattern matching macros (has_attributes!,get_attributes!, etc.)anyhow-result-#[anyhow_result]for usinganyhow::Result<TokenStream>in proc-macrosadd-code-#[add_code]for injecting code before/after function bodies
Helper Utilities (granular control):
context-context!()macro for manual error context with file/line infotokens-builder-TokensBuilderfor incrementally building token streamsindexed-name-indexed_name()for generating indexed identifiersfind-crate-find_crate()andfind_crate_list()for locating crates with rename supportparse-macro-input-parse_macro_input!()with automaticcompile_error!on parse failureexpr-error-wrap-expr_error_wrap()utilities for wrapping expressionsreadable-token-stream- Token stream formatting utilitiestoken-stream-consistent- Consistent token stream string conversion
Integration Features
easy-sql- Integration with Easy-SQL crate- Adds Easy-SQL support to
#[always_context]and attribute macros - Optional: only needed if using Easy-SQL in your project
- Adds Easy-SQL support to
License
Apache License, Version 2.0 - See LICENSE
Dependencies
~12–20MB
~447K SLoC