Skip to content

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.