Error handling
Separate delivery failures from process failures and select retries with an explicit class and safe code.
An invalid order and a Consumer that cannot maintain its RabbitMQ connection are different failures. Handlers, extractors, middleware and guards use QueueHandlerError to select a delivery outcome. ConsumerError represents startup and supervised process lifecycle failures.
The application chooses the class#
use lilyrs::queue::QueueHandlerError;
fn validate_order_id(id: &str) -> Result<(), QueueHandlerError> {
if id.is_empty() {
return Err(QueueHandlerError::permanent("ORDER_ID_REQUIRED"));
}
Ok(())
}| Class | Suitable example | Normal policy |
|---|---|---|
permanent |
Unsupported or permanently invalid data | Dead-letter handoff |
retryable |
Temporarily unavailable dependency | Retry when attempts remain; otherwise dead letter |
Malformed JSON is rejected permanently by the built-in extractor; domain rules such as positive amounts or active accounts belong to the application. Do not assume every database error is temporary. With retry_attempts=0, even retryable failures normally go to the DLQ.
Codes and source errors#
A code is an &'static str of 1–64 ASCII bytes matching [A-Za-z0-9][A-Za-z0-9_.-]*. An invalid code is replaced by a safe framework code for that class. Do not put event IDs, email addresses or variable error text in the code; choose a stable classification.
use lilyrs::queue::QueueHandlerError;
fn temporarily_unavailable(error: std::io::Error) -> QueueHandlerError {
QueueHandlerError::retryable_with_source("DEPENDENCY_UNAVAILABLE", error)
}permanent_with_source and retryable_with_source preserve the original error through std::error::Error::source. Standard Display and Debug omit sensitive source text, but your own logs can still expose it. Choose the class explicitly in Into<QueueHandlerError> when using a custom handler error type.
Preserve the first pipeline failure#
A normal middleware after_delivery error can turn success into failure; it cannot erase an existing primary failure. Panic, timeout and framework cancellation are not converted into success. An on_delivery_termination error is cleanup evidence; it cannot rewrite the earlier result or select an ACK.
QueueDeliveryOutcome is a pipeline view without source errors or payloads. is_success() does not prove a broker ACK, transaction commit or completed scope cleanup. The settlement page explains the next boundary.
Observe the process result#
An ordinary business rejection does not by itself terminate the Consumer process. Settlement, provider or cleanup failures can terminate the supervised runtime. Do not discard the result of Consumer::run().await or managed wait/shutdown. ConsumerLifecycleFailures::primary() preserves the first failure, while secondary_failures() retains later cleanup observations.
Inspect the shutdown report even after a failed shutdown. Logging only the final disposal error can hide the operation that originally failed.
Sources for this page v0.1.0
This guide is based on the reviewed framework commit.