Calleen
I've been writing production Rust applications for quite a few years now, and in every new project I find myself replicating certain patterns. This library provides what I would consider "best practices" when sending an HTTP request, and parsing its response.
To avoid the XY problem, let me first describe the problems I wanted to solve:
serde/serde_jsondon't retain the raw data when they fail to deserialize. This means that you'll get error logs that say "failed to deserialize" but have no insight into what the bad input was. As an individual, it is easy to work around this. But, as a team, it slips through pretty frequently, especially with engineers new to Rust and on-call log debugging.- Retry logic built in to the call layer that is HTTP-response-code aware -- I've been in many projects where we have ad-hoc retry logic at the callsite. And sometimes it knows not to retry e.g. 4xx errors, and only to retry 5xx errors. Sometimes it doesn't know.
- Critical failures and non-actionable were not disambiguated, meaning you could get paged when on-call for a third party 5xx response. Something you as an engineer can do nothing about!
This library addresses these three concerns primarily.
calleenretains the raw response, so if deserialization fails, the error log contains the raw input. This does have some memory overhead, but it is worth it. As somebody who has been paged at 1am for a serde deserialization failure many times in his life, I will always spend these bytes.- Centralized retry strategy definitions which are status-code aware and reasonably customizable.
- Disambiguation among various failure modes --
tracing::warn!()on typically non-actionable responses like 5xx,tracing::error!()on4xxor failure to deserialize response types, which are typically actionable and urgent. For companies I've worked in, we typically page onerror!()logs, so this triggers our PagerDuty.
Features
- Type-safe requests and responses - Generic over request/response types with automatic JSON serialization
- Rich error handling - Comprehensive error types with access to raw responses and HTTP details
- Flexible retry logic - Exponential backoff, linear, or custom retry strategies
- Customizable retry predicates - Retry on 5xx, timeouts, network errors, or custom conditions
- Automatic logging - Structured logging with
tracingfor observability - Response metadata - Access latency, status codes, headers, retry attempts, and raw response bodies
- Builder pattern - Fluent API for configuring clients
- Connection pooling - Reusable clients with efficient connection management
Installation
Add this to your Cargo.toml:
[]
= "0.1"
= { = "1.0", = ["full"] }
= { = "1.0", = ["derive"] }
Quick Start
use ;
use ;
use Duration;
async
Error Handling
Calleen provides detailed error information while preserving raw response data for debugging:
use ;
match client..await
Error Types
Network(reqwest::Error)- Network-level errors (connection failed, DNS, etc.)Timeout- Request timeoutDeserializationFailed { raw_response, serde_error, status }- Failed to parse responseHttpError { status, raw_response, headers }- Non-2xx HTTP statusConfigurationError(String)- Invalid client configurationMaxRetriesExceeded { attempts, last_error }- All retry attempts exhaustedSerializationFailed(String)- Failed to serialize request bodyInvalidUrl(url::ParseError)- Invalid URL
Retry Strategies
Exponential Backoff (Recommended)
use ;
use Duration;
let client = builder
.base_url?
.retry_strategy
.build?;
Delays: 100ms, 200ms, 400ms, 800ms, 1600ms... (with random jitter)
Linear Backoff
let client = builder
.base_url?
.retry_strategy
.build?;
Delays: 1s, 1s, 1s...
Custom Retry Logic
let client = builder
.base_url?
.retry_strategy
.build?;
Custom Retry Predicates
Control when to retry based on error type, status code, or custom logic:
use ;
// Retry on rate limit errors (HTTP 429)
;
// Combine predicates: retry on 5xx OR timeouts OR rate limits
let client = builder
.base_url?
.retry_predicate
.build?;
Built-in Predicates
RetryOnRetryable- Retry on network errors, timeouts, and 5xx errors (default)RetryOn5xx- Retry only on 5xx server errorsRetryOnTimeout- Retry only on timeout errorsRetryOnConnectionError- Retry only on network/connection errorsOrPredicate- Combine predicates with OR logicAndPredicate- Combine predicates with AND logic
Response Metadata
Access detailed information about each request:
let response = client..await?;
println!;
println!;
println!;
println!;
println!;
println!;
println!;
Advanced Usage
Custom Headers
let client = builder
.base_url?
.default_header?
.default_header?
.build?;
Request Metadata
use RequestMetadata;
use Method;
let metadata = new
.with_header?
.with_query_param
.with_query_param;
let response = client..await?;
All HTTP Methods
// GET
let response = client..await?;
// POST
let response = client..await?;
// PUT
let response = client..await?;
// DELETE
let response = client..await?;
// PATCH
let response = client..await?;
Logging
Calleen uses the tracing crate for structured logging. Initialize a subscriber to see logs:
fmt
.with_env_filter
.init;
Log levels:
debug- Request details, serializationinfo- Response received, latencywarn- Retries, 5xx errorserror- 4xx errors, deserialization failures
Examples
Run the examples to see Calleen in action:
# Basic GET and POST requests
# Different retry strategies
# Comprehensive error handling
# Custom retry predicates
Why Calleen?
vs. Raw reqwest
| Feature | Calleen | reqwest |
|---|---|---|
| Automatic retries | ✅ | ❌ |
| Rich error types with raw response | ✅ | ❌ |
| Built-in logging | ✅ | ❌ |
| Response metadata (latency, attempts) | ✅ | ❌ |
| Type-safe requests/responses | ✅ | ✅ |
| Connection pooling | ✅ | ✅ |
Calleen builds on top of reqwest to provide a higher-level, more production-ready API client experience.
Design Philosophy
- Preserve debugging information - Always keep raw responses, error messages, and metadata
- Type safety - Leverage Rust's type system for compile-time guarantees
- Sensible defaults - Works out of the box, configurable when needed
- Composability - Retry predicates, strategies, and headers are all composable
- Observability - Built-in logging and metrics-friendly design
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
License
This project is licensed under the MIT License - see the LICENSE file for details.
Comparison with Similar Libraries
- reqwest - Low-level HTTP client. Calleen builds on reqwest with retries and error handling.
- surf - Async HTTP client with middleware. Calleen focuses on retry logic and error context.
- ureq - Synchronous HTTP client. Calleen is async-first with tokio.
Future Roadmap
- Rate limiting support
- Circuit breaker pattern
- Request/response middleware chain
- Metrics collection hooks
- Mock mode for testing
- Connection pooling configuration
- Support for other serialization formats (XML, protobuf)