Exports, Imports, and Tasks¶
Exports, export-template execution, and imports are asynchronous server operations. They return task-shaped responses that can be inspected through client.tasks() or handled through higher-level helpers that submit, poll, and fetch output.
Export Templates¶
Export templates are exposed as a regular resource. Executable templates need a scope, while fragment templates can use ExportTemplateKind::Fragment and omit the execution metadata:
let template = client
.export_templates()
.create_checked()
.collection_id(7)
.name("owner-export")
.description("Owner listing")
.content_type(hubuum_client::ExportContentType::TextPlain)
.template("{{#each items}}{{this.name}}\n{{/each}}")
.kind(hubuum_client::ExportTemplateKind::Export)
.scope_kind(hubuum_client::ExportScopeKind::ObjectsInClass)
.class_id(42)
.send()?;
Direct Exports¶
Submitting a direct export creates a task, and the rendered output is fetched once the task finishes. client.exports().run(...) is the high-level helper that submits, polls the task to completion, and returns a typed ExportResult:
let request = hubuum_client::ExportRequest {
limits: None,
missing_data_policy: None,
query: Some("name__icontains=server".to_string()),
scope: hubuum_client::ExportScope {
class_id: Some(42.into()),
kind: hubuum_client::ExportScopeKind::ObjectsInClass,
object_id: None,
},
include: None,
relation_context: None,
};
let export = client.exports().run(request).send()?;
match export {
hubuum_client::ExportResult::Json(body) => println!("{} rows", body.items.len()),
hubuum_client::ExportResult::Rendered { body, .. } => println!("{body}"),
}
The polling cadence and deadline are configurable, and the flow can also be driven manually with low-level helpers:
use std::time::Duration;
let export = client
.exports()
.run(request.clone())
.poll_interval(Duration::from_millis(500))
.timeout(Some(Duration::from_secs(120)))
.send()?;
let task = client.exports().submit(request).send()?;
let task = client.tasks().wait(task.id).send()?;
let output = client.exports().output(task.id)?;
Template-Backed Exports¶
Executable templates use the backend's dedicated route:
let output = client
.export_templates()
.run_export(7, hubuum_client::ExportTemplateRunRequest::default())
.poll_interval(Duration::from_millis(500))
.send()?;
match output {
hubuum_client::ExportResult::Rendered { content_type, body } => {
assert_eq!(content_type, hubuum_client::ExportContentType::TextPlain);
println!("{body}");
}
hubuum_client::ExportResult::Json(body) => println!("{} rows", body.items.len()),
}
Use client.export_templates().submit_export(template_id, request) when you want to submit the task but control polling and output retrieval yourself.
Migration from the old report API is a rename plus one route split: use Export* types instead of Report*, client.exports() instead of client.reports(), client.export_templates() instead of client.templates(), and ExportTemplateRunRequest for template-backed exports.
Imports¶
Use run() to submit an import, wait for its terminal task state, and collect all result rows:
let imported = client
.imports()
.run(
hubuum_client::ImportRequest::new(hubuum_client::ImportGraph::default())
.dry_run(true),
)
.idempotency_key("inventory-preview-2026-07-11")
.send()?;
println!("{} succeeded, {} failed", imported.succeeded(), imported.failed());
Failed and cancelled tasks return ApiError::TaskUnsuccessful without fetching result rows. Use distinct idempotency keys for dry-run and mutating imports.
ImportGraph and ImportRequest remain the source-compatible core graph
surface. Use FullImportGraph, FullImportRequest, and run_full() when an
import also contains identity scopes, groups, principals, memberships, export
templates, remote targets, event sinks, event subscriptions, or class-relation
template aliases:
let mut graph = hubuum_client::FullImportGraph::default();
graph
.identity_scopes
.push(hubuum_client::ImportIdentityScopeInput {
ref_: Some("scope:directory".into()),
name: "directory".into(),
provider_kind: "ldap".into(),
timestamps: None,
});
let imported = client
.imports()
.run_full(hubuum_client::FullImportRequest::new(graph).dry_run(true))
.idempotency_key("full-inventory-preview-2026-07-23")
.send()?;
FullImportGraph is non-exhaustive so additional server sections can be added
without breaking downstream struct literals. Start with default() and append
typed items to its section vectors. Existing ImportRequest values can be
upgraded with FullImportRequest::from(request).
The lower-level helpers remain available when polling must be controlled directly:
let task = client
.imports()
.submit(hubuum_client::ImportRequest::new(
hubuum_client::ImportGraph::default(),
).dry_run(true))
.idempotency_key("inventory-import-2026-03-07")
.send()?;
let task_state = client.tasks().get(task.id)?;
let event_page = client.tasks().events(task.id).limit(50).page()?;
let result_page = client.imports().results(task.id).limit(50).page()?;
Task Listing¶
Tasks can be listed and filtered with cursor-paged raw query parameters:
let tasks = client
.tasks()
.query()
.kind(hubuum_client::TaskKind::Export)
.status(hubuum_client::TaskStatus::Succeeded)
.limit(50)
.list()?;
Cursor-paged endpoints return hubuum_client::Page<T> with items and next_cursor.
Large rendered outputs can bypass in-memory buffering. Blocking clients expose a
Read implementation through output_stream(task_id), while async clients
return a byte stream and support download_output(task_id, path).
Path downloads are written to a uniquely named sibling temporary file and only
replace the destination after the complete response has been written and
flushed. This publication step is atomic on supported platforms, so a failed
download leaves an existing destination unchanged and removes the partial
temporary file. The helper does not call fsync/sync_all; successful return
does not guarantee that the file or containing directory has reached durable
storage after a system failure.