Replay-safe requests retry 408, 429, 502, 503, and 504 responses with
jittered exponential backoff and Retry-After support. GET, HEAD, and
OPTIONS are replay-safe; mutating requests retry only when an idempotency key
is present. Configure or disable the policy on the client builder:
use std::time::Duration;
use hubuum_client::RetryPolicy;
let client = hubuum_client::Client::builder_from_url("https://hubuum.example")?
.retry_policy(RetryPolicy {
max_attempts: 5,
initial_delay: Duration::from_millis(100),
max_delay: Duration::from_secs(3),
})
.max_response_body_bytes(16 * 1024 * 1024)
.max_error_body_bytes(64 * 1024)
.build()?;Normal responses and error previews have independent limits. Exceeding the
normal limit returns ApiError::ResponseTooLarge. For unified-search streams,
the normal limit applies independently to each buffered SSE event so a long
stream remains supported without allowing one event to grow without bound.
Streaming export methods are the intended path for larger payloads.
The built-in async and blocking HTTP clients do not follow redirects. A redirect
response is surfaced as an ApiError with its 3xx status so an authenticated
request cannot move away from the endpoint constructed by the client.
Supplying a preconfigured reqwest client with with_http_client also supplies
its redirect policy. Keep redirects disabled when the configured Hubuum URL
shares an origin with other applications or uses a restricted path prefix.
Authenticated administrators can inspect the server's effective, redacted
process configuration. Secret-bearing values are represented by SecretStatus,
which reports only whether each value is configured.
let config = client.admin_config().await?;
println!("page limit: {}", config.pagination.max_page_limit);
println!("TLS enabled: {}", config.server.tls.enabled);
println!("backup retention: {} hours", config.backups.output_retention_hours);
println!("permission backend: {}", config.permissions.backend);The endpoint is read-only. Secret values are never returned; fields such as
treetop_url, database.url, and provider_config_path expose configuration
status only.
Authenticated clients can inspect complete PostgreSQL and process-local pool
telemetry with meta_db_full():
let db = client.meta_db_full().await?;
println!("{} of {} connections in use", db.in_use_connections, db.max_connections);
println!("{} acquisitions timed out", db.acquisitions_timed_out);
println!("{} ms spent waiting", db.acquisition_wait_time_ms);The original meta_db() response remains available for source compatibility.
Use the full response when monitoring pool capacity, acquisition pressure,
connection churn, or database size.
Prometheus exposition text is available without bearer authentication. The
default server path is /metrics:
let metrics = client.metrics().await?;
println!("{metrics}");The server may configure a different literal path. Administrators can discover
it through the read-only effective configuration endpoint and pass it directly
to metrics_at:
let config = client.admin_config().await?;
let metrics = client.metrics_at(&config.server.metrics_path).await?;Both methods are available before and after login and deliberately omit the
Authorization header. The server still applies its client allowlist. Scrapes
use the client's normal retry and response-body limit policies.
Async resource and cursor builders provide pages() and items() streams.
Blocking builders return iterators with the same methods. Both lazy forms honor
the client's configured automatic-pagination page and item limits. Unified
search stream() parses SSE frames as they arrive and preserves future event
names as UnifiedSearchEvent::Unknown.
use futures_util::TryStreamExt;
let classes = client
.collection(collection_id)
.classes()
.name()
.contains("server")
.items();
futures_util::pin_mut!(classes);
while let Some(class) = classes.try_next().await? {
println!("{}", class.name);
}typed_class::<T>(class_id) decodes object data directly into T. With the
typed-schemas feature, a collection scope can create a class schema from a
type implementing schemars::JsonSchema.
#[derive(serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
struct Server {
hostname: String,
cores: u16,
}
let server = client
.typed_class::<Server>(server_class_id)
.get(object_id)
.await?;
println!("{}", server.data.hostname);MockTransport records transport-neutral RequestPlan values and returns queued
TransportResponse values. Recorded diagnostics redact authorization, query
values, and bodies. A configured transport handles every client operation,
including login and token validation, public discovery and probes, raw and typed
API calls, export downloads, and unified-search streams. Buffered calls honor
the normal retry and body-limit policies.
use std::sync::Arc;
use hubuum_client::{MockTransport, Token, TransportResponse};
let transport = MockTransport::default();
transport.push_response(TransportResponse::json(
reqwest::StatusCode::OK,
&serde_json::json!({"status": "ok"}),
)?);
let client = hubuum_client::Client::builder_from_url("https://mock.invalid")?
.with_transport(Arc::new(transport.clone()))
.build()?
.authenticate(Token::new("test-token"));Implement AsyncTransport or BlockingTransport to integrate a different HTTP
stack. RequestPlan clones share their immutable serialized body, so custom
transports can retain or retry a plan without copying a potentially large
payload. Because the transport traits return buffered TransportResponse
values, streaming bodies are exposed as one stream chunk or one blocking
reader; the built-in HTTP transports remain incremental. Successful streaming
payloads are not subject to the buffered-response size limit in either case,
while retry and error-body limits still apply. The lower-level
client.raw(method, relative_path) escape hatch can call new server routes
before the typed library catches up. It rejects absolute URLs, network-path
references, decoded parent traversal, query or fragment injection, configured
base-prefix escapes, and authorization overrides before attaching a bearer
token.