Expand description
§beckon
Generate type-safe, async HTTP clients from endpoint definitions.
beckon! takes a client name and a list of endpoints, and expands to a struct with one
async method per endpoint — plus a trait for mocking and a typed error enum. No hand-written
request boilerplate.
§Install
cargo add beckonYou’ll also need the runtime dependencies the generated code uses:
cargo add reqwest --features json
cargo add serde --features derive
cargo add tokio --features fullRequires Rust 1.75+ — the generated mocking trait uses async fn in traits.
§Example
use beckon::beckon;
use serde::{Deserialize, Serialize};
use std::time::Duration;
#[derive(Serialize, Deserialize)]
pub struct User {
pub id: u32,
pub name: String,
}
#[derive(Serialize)]
pub struct UserPath {
pub id: u32,
}
beckon!(
UserApi,
{
{
path: "/users",
method: GET,
res: Vec<User>,
},
{
path: "/users/{id}",
method: GET,
path_params: UserPath,
res: User,
},
{
path: "/users",
method: POST,
req: User,
res: User,
},
}
);
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = UserApi::new(
reqwest::Url::parse("https://api.example.com")?,
Duration::from_secs(5),
);
let users = client.get_users().await?;
let user = client.get_users_by_id(&UserPath { id: 1 }).await?;
Ok(())
}§Endpoint Fields
Required:
method: HTTP method (GET,POST,PUT,DELETE,PATCH)
Optional:
path: URL path (e.g.,"/users/{id}")res: Response type (defaults to())req: Request body typepath_params: Type for path parametersquery_params: Type for query parametersheaders: Header type (e.g.,reqwest::header::HeaderMap)fn_name: Custom method nameretries: Retry count for this endpoint (overrides the global setting)
Method names are derived from the method and path — GET /users → get_users,
GET /users/{id} → get_users_by_id. Set fn_name to override.
§Auth
Add automatic authentication to every request. Three strategies are supported:
// Bearer token — injects `Authorization: Bearer <token>`
beckon!(GithubApi, auth: Bearer, { /* ... */ });
let client = GithubApi::new(url, "ghp_xxxx", Duration::from_secs(5));
// Basic auth — injects `Authorization: Basic <base64>`
beckon!(DbApi, auth: Basic, { /* ... */ });
let client = DbApi::new(url, "admin", "secret", Duration::from_secs(5));
// API key — injects a custom header
beckon!(StripeApi, auth: ApiKey("X-Api-Key"), { /* ... */ });
let client = StripeApi::new(url, "sk_live_xxxx", Duration::from_secs(5));Omitting auth keeps the plain new(url, timeout) constructor. Auth composes with every
other feature, including retries.
§Retry with Backoff
Set a global retry count that applies to all endpoints. Retries use exponential backoff (100ms base, 2x multiplier, 5s cap) and trigger on 5xx errors and request timeouts. 4xx errors are never retried.
beckon!(
UserApi,
retries: 3,
{
{
path: "/users",
method: GET,
res: Vec<User>,
// inherits retries: 3 from the global setting
},
{
path: "/health",
method: GET,
retries: 0, // override: no retries for this endpoint
},
}
);Per-endpoint retries overrides the global value. Omitting retries entirely means no retries.
§Constructors
UserApi::new(url, timeout)— uses a defaultreqwest::Client.UserApi::with_client(url, client, timeout)— supply your ownreqwest::Clientto share a connection pool, TLS config, proxy, or default headers.
timeout accepts a std::time::Duration, or None for the 5-second default.
let http = reqwest::Client::builder().user_agent("my-app/1.0").build()?;
let client = UserApi::with_client(url, http, Duration::from_secs(5));§Errors
Every method returns Result<Res, UserApiError>. On a non-2xx response you get the
Http variant, which carries the server’s response body so you can see why a request
was rejected:
match client.get_users_by_id(&UserPath { id: 999 }).await {
Ok(user) => { /* ... */ }
Err(UserApiError::Http { status, reason, body }) => {
eprintln!("HTTP {status} {reason}: {body}"); // body = the server's error payload
}
Err(other) => eprintln!("{other}"),
}The enum is #[non_exhaustive], so matching downstream should keep a _ arm.
§Generated Code
For a client named UserApi, the macro generates:
- A struct
UserApi(derivesClone) withnewandwith_clientconstructors - An async method for each endpoint
- A trait
UserApiTraitfor mocking in tests - An error enum
UserApiErrorwith variants for URL, request, HTTP, and deserialization errors
§Examples
See the examples/ directory:
basic.rs— simple GET requestsparams.rs— path and query parametersadvanced.rs— all featuresmocking.rs— testing with the generated traitmultiple_path_params.rs— nested resourceserror_handling.rs— inspecting a failed request’s body
§License
Licensed under either of
at your option.
Macros§
- beckon
- Generate a type-safe, async HTTP client from endpoint definitions.