Rust/Tokio HTTP client API Documentation
http-client-rust-tokio is the JSON HTTP runtime used by Rust SDKs generated by rust-openapi and rust-openapi-crate-gen. It uses Reqwest for I/O and returns a typed success/error enum based on the numeric status class.
[dependencies]
http-client-rust-tokio = { path = "../alt-stack/packages/http-client-rust-tokio" }
serde = { version = "1", features = ["derive"] }
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
The crate uses Rust 2021 and is a workspace crate in this repository. Replace the path with a version only when http-client-rust-tokio is available from your configured Cargo registry. Generated SDK crates normally expose it as default_http_client.
Complete example
use http_client_rust_tokio::{ApiClient, ApiClientOptions, ApiResponse, JsonRequest};
use serde::Deserialize;
#[derive(Debug, Deserialize)]
struct User {
id: String,
name: String,
}
#[derive(Debug, Deserialize)]
struct ApiError {
code: String,
message: String,
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = ApiClient::new(ApiClientOptions::new("http://127.0.0.1:3000"));
let request = JsonRequest::new()
.with_path_param("id", "u_1")
.with_query(&serde_json::json!({ "includeProfile": true }))?;
match client.get::<User, ApiError>("/users/{id}", request).await? {
ApiResponse::Success(response) => println!("{} {}", response.status, response.body.name),
ApiResponse::Error(response) => eprintln!("{} {}", response.status, response.body.message),
}
Ok(())
}
This example assumes a JSON API is listening on port 3000.
ApiClientOptions
pub struct ApiClientOptions {
pub base_url: String,
pub headers: HeaderMap,
pub timeout: Option<Duration>,
}
ApiClientOptions::new(base_url) trims every trailing /, creates an empty header map, and sets no default timeout. Struct construction can override all fields.
ApiClient
ApiClient::new(options) creates a new Reqwest Client and stores the normalized base URL, default headers, and default timeout.
The convenience methods get, post, put, patch, and delete all have the same generic contract:
async fn get<TSuccess, TError>(
&self,
path: &str,
request: JsonRequest,
) -> Result<ApiResponse<TSuccess, TError>, ApiClientError>
where
TSuccess: DeserializeOwned,
TError: DeserializeOwned;
execute_json(method: HttpMethod, path, request) exposes the same behavior for any Reqwest method. The crate re-exports reqwest::Method as HttpMethod.
Execution concatenates the normalized base URL with the interpolated path, overlays request headers on client headers, applies the request timeout before the client default, adds query pairs, serializes an optional JSON body, and sends the request. Statuses 200..=299 deserialize into TSuccess; every other status deserializes into TError.
There is no retry policy or schema validation in this runtime. Generated Rust types plus Serde provide response decoding; request data is serialized without an OpenAPI validation pass.
JsonRequest
#[derive(Debug, Clone, Default)]
pub struct JsonRequest {
pub path_params: Vec<(String, String)>,
pub query: Option<serde_json::Value>,
pub body: Option<serde_json::Value>,
pub headers: HeaderMap,
pub timeout: Option<Duration>,
}
Builder methods consume and return Self:
| Method | Result |
|---|---|
new() | empty request |
with_path_param(key, value) | appends a replacement pair |
with_query(&T) | serializes T: Serialize to a JSON value or returns ApiClientError::Serialize { kind: "query", ... } |
with_body(&T) | serializes T: Serialize or returns Serialize { kind: "body", ... } |
with_headers(HeaderMap) | replaces the request header map |
with_timeout(Duration) | sets a per-request timeout |
Path replacement is literal and not URL-encoded. Query serialization only expands a top-level JSON object: scalar values become one pair, arrays become repeated keys, null values are omitted, and nested objects become a compact JSON string. A non-object query value produces no pairs.
When a body exists and neither default nor request headers supply Content-Type, the client adds application/json. Request headers override defaults by header name.
Responses
pub struct JsonResponse<T> {
pub status: u16,
pub headers: HeaderMap,
pub body: T,
}
pub enum ApiResponse<TSuccess, TError> {
Success(JsonResponse<TSuccess>),
Error(JsonResponse<TError>),
}
ApiResponse::is_success() tests the variant. ApiResponse::status() returns the status from either variant. Response headers are cloned before the body is read. Empty or whitespace-only response text is normalized to JSON null, which allows () and other null-compatible success types for 204 responses.
The crate re-exports reqwest::header::HeaderMap as DefaultHeaderMap.
ApiClientError
pub enum ApiClientError {
Serialize { kind: &'static str, source: serde_json::Error },
Request(reqwest::Error),
Deserialize {
kind: &'static str,
status: u16,
source: serde_json::Error,
body: String,
},
}
Serializeidentifies query or body conversion.Requestcovers Reqwest request construction, transport, timeout, and body-read failures.Deserializeidentifies"success"or"error", preserves the status and full normalized body, and exposes the Serde JSON error.
HTTP 4xx/5xx statuses are not ApiClientError; they are Ok(ApiResponse::Error(...)) when the body decodes as TError. A body that does not decode into the selected type is a Deserialize error regardless of status.
Export checklist
The crate exports ApiClientOptions, ApiClient, JsonRequest, JsonResponse, ApiResponse, ApiClientError, DefaultHeaderMap, and HttpMethod, including every public field and method described above.