modo-macros
Procedural macros for the modo web framework. Provides attribute macros for route registration and application bootstrap, plus derive macros for input validation and sanitization.
All macros are re-exported from modo — import them as modo::handler,
modo::main, etc. Do not depend on modo-macros directly in application code.
Features
| Feature | What it enables |
|---|---|
static-embed |
#[main(static_assets = "...")] static file embedding via rust-embed |
Template and i18n macros (#[view], #[template_function], #[template_filter], t!)
are only active when the corresponding templates or i18n feature is enabled
on the modo crate.
Usage
Application entry point
async
The function must be named main, be async, and accept exactly two
parameters: an AppBuilder and a config type that implements
serde::de::DeserializeOwned + Default. The macro replaces the function with a
sync fn main() that bootstraps a multi-threaded Tokio runtime, configures
tracing_subscriber (using RUST_LOG or falling back to
"info,sqlx::query=warn"), loads config via modo::config::load_or_default,
and exits with code 1 on error.
The return type annotation on the async fn main is not enforced by the macro;
write it for readability but the body is wrapped internally.
Embedding static files
async
Requires the static-embed feature on modo-macros.
HTTP handlers
async
async
Supported methods: GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS.
Path parameters written as {name} are extracted automatically. Declare a
function parameter with the matching name and the macro injects
axum::extract::Path extraction. Undeclared path params are captured but
ignored (partial extraction).
Handler-level middleware
async
// Factory middleware (called with arguments)
async
Bare middleware paths are wrapped with axum::middleware::from_fn. Paths
followed by (args) are called as layer factories. Multiple middleware entries
are applied in the order listed.
Route modules
// With module-level middleware
All #[handler] attributes inside the module are automatically associated with
the module's prefix and middleware at compile time via inventory.
Bare mod foo; declarations inside the module body are allowed. Inline nested
mod foo { ... } blocks are not supported and produce a compile error, because
their handlers would not receive the outer prefix.
Custom error handler
The function must be sync and accept exactly (modo::Error, &modo::ErrorContext).
It is registered via inventory and invoked for every unhandled modo::Error.
Only one error handler may be registered per binary.
Input sanitization
Available #[clean(...)] rules: trim, lowercase, uppercase,
strip_html_tags, collapse_whitespace, truncate = N, normalize_email,
custom = "path::to::fn".
Sanitization runs automatically inside JsonReq and FormReq extractors.
Generic structs are not supported.
Input validation
// In a handler:
use JsonReq;
async
Available #[validate(...)] rules: required, min_length = N,
max_length = N, email, min = V, max = V, custom = "path::to::fn".
Each rule accepts an optional (message = "...") override. A field-level
message = "..." key is used as a fallback for all rules on that field.
Templates (requires templates feature on modo)
// With a separate HTMX partial
Localisation (requires i18n feature on modo)
// In a handler with an I18n extractor:
let msg = t!;
let items = t!;
t! calls .t_plural on the i18n context when a count variable is present,
selecting the correct plural form.
Key Macros
| Macro | Kind | Purpose |
|---|---|---|
#[handler] |
attribute | Register an async fn as an HTTP route |
#[main] |
attribute | Application entry point and runtime bootstrap |
#[module] |
attribute | Group routes under a shared URL prefix |
#[error_handler] |
attribute | Register a custom error handler |
Sanitize |
derive | Generate Sanitize::sanitize from #[clean] fields |
Validate |
derive | Generate Validate::validate from #[validate] fields |
t! |
function-like | Localisation key lookup with variable substitution |
#[view] |
attribute | Link a struct to a MiniJinja template |
#[template_function] |
attribute | Register a MiniJinja global function |
#[template_filter] |
attribute | Register a MiniJinja filter |