lilydocs
HTTP APIWebSocketConsumer
Pipeline3 min read

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#

Rust
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.

Rust
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.

QUICK START
Documentation
Source code