A Rust client library for the Hubuum API. It provides synchronous and asynchronous clients, type-state authentication, typed resource IDs, fluent query builders, and task helpers for long-running operations such as imports and exports.
hubuum_client 0.9.0 targets Hubuum server v0.0.9. The exact tested image and
the history for earlier client releases are recorded in
COMPATIBILITY.md.
- Type-state authentication: unauthenticated clients can only log in; authenticated clients expose the full API.
- Async and blocking clients: use
hubuum_client::Clientfor async code orhubuum_client::blocking::Clientfor synchronous applications. - Configurable setup: use
Client::from_url("https://...")for secure defaults or a client builder for certificate validation, timeout, and user-agent controls. - Typed resource access: collections, classes, objects, relations, users, groups, permissions, remote targets, event sinks, export templates, imports, and tasks use typed request and response models.
- Fluent queries and pagination: chain typed filters directly from resource helpers and choose
list(),page(),all(), orone()depending on the result shape you need. - Lazy and bounded I/O: stream cursor pages, individual items, search events, and export bytes without buffering entire result sets.
- Safe extension points: inject a custom or mock transport, issue authenticated requests to newly added relative routes, and configure retries and response-size limits.
- Typed object payloads: decode object
datainto application structs and optionally derive JSON Schema with thetyped-schemasfeature. - Exports, complete graph imports, and full-system backups: submit asynchronous work, poll task state, and fetch typed outputs with high-level helpers.
- Administrative recovery: inspect redacted runtime configuration and stage, confirm, or inspect destructive restores with explicit capability handling.
- Computed fields: manage shared class definitions and personal definitions, preview expressions, request rebuilds, and read enriched objects.
- Natural-key routing: address classes and objects by exact names, including numeric-looking names, across CRUD, permissions, relations, and graph operations.
- Object aggregates and patching: group permission-visible objects by typed dimensions, filter or sort by computed fields, and atomically patch object data with RFC 6902 documents.
- Effective public configuration: discover pagination limits and the default token lifetime, and inspect the server-applied page limit alongside cursors and optional totals.
- Principal-centric identity: users and service accounts are principals, with group membership, scoped tokens, and effective permission helpers.
- Scoped identity providers: discover available providers before login, authenticate against named scopes, filter principals by scope, inspect provider ownership, and disambiguate scoped group references in imports.
- Principal settings: get, replace, merge-patch, or reset object-only preferences for the current or an explicitly selected principal.
- Health and readiness probes: unauthenticated
healthz()andreadyz()calls are available for operational checks. - Prometheus metrics: fetch exposition text without bearer authentication from the default or administratively configured metrics path.
Add the dependency to your project's Cargo.toml:
[dependencies]
hubuum_client = "0.9.0"Async support is enabled by default. Blocking applications can opt into only the synchronous surface:
[dependencies]
hubuum_client = { version = "0.9.0", default-features = false, features = ["blocking"] }If you need unreleased changes, point Cargo at the Git repository:
[dependencies]
hubuum_client = { git = "https://github.com/hubuum/hubuum-client-rust" }The root Client is asynchronous. Blocking users can use hubuum_client::blocking::Client; the blocking API mirrors the async surface without .await.
use hubuum_client::{Client, Credentials};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = Client::from_url("https://server.example.com:443")?
.login(Credentials::new("foo", "secret"))
.await?;
let class = client
.classes()
.create_checked()
.name("example-class")
.collection_id(1)
.description("Example class")
.send()
.await?;
let matches = client
.classes()
.name()
.contains("server")
.limit(25)
.list()
.await?;
println!("created class {} and found {} matches", class.id, matches.len());
Ok(())
}Resource identity is typed. Handles and resources expose IDs such as ClassId, CollectionId, ObjectId, and GroupId, so accidentally passing a group ID to classes().get(...) is rejected at compile time. Carry these types through application code; fluent API boundaries accept owned IDs, borrowed IDs, integer literals, and raw i32 values. Use .get() only when crossing into an intentionally untyped or polymorphic interface.
Collections are the top-level organizational resource for classes, objects, export templates, imports, remote targets, and scoped permissions:
let collection = client
.collections()
.create_checked()
.name("platform")
.description("Platform inventory")
.group_id(admin_group_id)
.parent_collection_id(parent_collection_id)
.send()
.await?;
let children = collection.children().await?;
let ancestors = collection.ancestors().await?;
let moved = collection.move_parent(new_parent_collection_id).await?;
let effective = collection.effective_group_permissions(admin_group_id).await?;Queries compose by chaining field operators. Use list() for one page, page() when you need cursor metadata, all() to follow pagination, and one() when exactly one result is expected:
let classes = client
.classes()
.name()
.icontains("server")
.created_at()
.gte(since)
.validate_schema()
.eq(true)
.limit(25)
.list()
.await?;Exact-name scopes avoid ambiguity when a class or object name contains only digits. The same scopes expose aggregates and atomic object-data patches:
use hubuum_client::{
ObjectAggregateDimension, ObjectDataPatchDocument, ObjectDataPatchOperation,
};
let class = client.class_by_name("123");
let object = class.objects().by_name("456");
let patch = ObjectDataPatchDocument::new([ObjectDataPatchOperation::Replace {
path: "/owner".into(),
value: serde_json::json!("network"),
}]);
object.patch_data(&patch).await?;
let page = class
.object_aggregates()
.group_by(ObjectAggregateDimension::Name)
.include_total(true)
.page()
.await?;
println!("server applied page limit {:?}", page.page_limit);Pagination defaults and the lifetime applied when token issuance omits an
explicit expiry are public and can be discovered before login with
Client::config(). Issued tokens preserve the server's authoritative expiry
through Token::expires_at(), while authenticated clients expose the login
expiry through Client::token_expires_at().
Exports and imports are task-backed. High-level helpers submit work, poll the task to completion, and return the final output:
let export = client.exports().run(request).send().await?;
match export {
hubuum_client::ExportResult::Json(body) => println!("{} rows", body.items.len()),
hubuum_client::ExportResult::Rendered { body, .. } => println!("{body}"),
}
let imported = client
.imports()
.run(hubuum_client::ImportRequest::new(graph))
.idempotency_key("inventory-import-2026-07-11")
.send()
.await?;
println!("{} changes applied", imported.succeeded());Backups use the same task pattern. Restore confirmation is intentionally a separate, destructive step and status inspection uses its one-time capability instead of bearer authentication:
let document = client
.backups()
.run(hubuum_client::BackupRequest::default())
.send()
.await?;
let staged = client.restores().stage(&document).await?;
let capability = staged.restore_capability.clone().expect("one-time capability");
let status = client.restore_status(staged.id, &capability).await?;Shared and personal computed fields use typed definitions. Enriched object reads opt into both computed scopes:
use hubuum_client::{ComputedFieldDefinitionRequest, ComputedFieldOperation, ComputedResultType};
let definition = ComputedFieldDefinitionRequest::new(
"total",
"Total",
ComputedFieldOperation::Sum { paths: vec!["/subtotal".into(), "/tax".into()] },
ComputedResultType::Number,
);
client.computed_fields(class_id).create(definition).await?;
let object = client.computed_object(class_id, object_id).await?;
println!("{:?}", object.computed.shared.values.get("total"));Unified search is exposed through client.search(...):
let search = client
.search("server")
.kinds([
hubuum_client::UnifiedSearchKind::Collection,
hubuum_client::UnifiedSearchKind::Object,
])
.limit_per_kind(5)
.search_object_data(true)
.send()
.await?;Principal settings preserve arbitrary JSON below an object root and expose the server's replace, merge-patch, and reset behavior directly:
let settings = client
.settings()
.patch(&serde_json::json!({ "theme": "dark" }))
.await?;Administrators can inspect effective permissions for either principal kind without first resolving it as a user or service account:
let permissions = client.principal_permissions(principal_id).await?;Operational clients can scrape Prometheus text before authentication. For a non-default path, use the value exposed by the read-only administrative config:
let metrics = client.metrics().await?;
let config = admin_client.admin_config().await?;
let configured_metrics = client.metrics_at(&config.server.metrics_path).await?;- Client setup: async and blocking initialization, token login, and builder options.
- Querying resources: resource CRUD, typed filters, pagination, related-object traversal, and error details.
- Exports, imports, and tasks: export templates, rendered output, task polling, and import results.
- Backups, restores, and computed fields: administrative recovery, capability handling, definition lifecycles, previews, rebuilds, and enriched reads.
- Advanced usage: lazy streams, retries, body limits, typed payloads, scoped navigation, mock transports, and raw requests.
- Principal settings: current-principal and administrative preference management with JSON Merge Patch.
- Scoped authentication: provider-scoped login, identity metadata, queries, and import references.
- Integration tests: Docker-backed real-server tests, e2e client tests, seed data, and environment variables.
- Resource code generation: repository-local specifications, checked-in output, and drift checks.
- Server compatibility: declared server targets, immutable test images, and historical compatibility evidence.
- Release procedure: crates.io release checklist and trusted publishing notes.
Release notes live in CHANGELOG.md.
Contributions are welcome. If you find issues or have suggestions for improvements, please open an issue or submit a pull request on GitHub.
Distributed under the MIT License. See LICENSE for more details.