aion-client
Rust caller SDK for connecting to an Aion server deployment and operating Aion workflows. The crate exposes connect plus the seven workflow operations: start, signal, query, cancel, list, describe, and subscribe.
Key public types
Client,ClientBuilder,ClientAuth, andTlsOptionsconfigure gRPC connections.WorkflowHandlescopes operations to a started workflow run.StartOptions,StartOutcome,DisplayNameNotApplied,WorkflowDescription, andListPagemodel workflow operations.EventStream,ResumingEventStream, andSubscribeTargetstream events.to_payloadandfrom_payloadbridge typed values withaion-corepayloads.
Install
[]
= "0.4.0"
Server prerequisite
Run an Aion server (aion server --config <file>) that implements the AW workflow API. The runnable example uses the AL-007 server fixture defaults:
# optional
See examples/seven_operations.rs for a complete program covering all seven operations.
Connect
use ;
let mut builder = new
.with_namespace;
if let Ok = var
let client = builder.build.await?;
start
start_typed serializes a typed value to JSON and returns a StartOutcome, not a bare handle: .handle is the WorkflowHandle carrying the workflow and run IDs, and .display_name_not_applied reports a display name an idempotent replay could not apply (see below). StartOptions::idempotency_key makes caller retries safe: the same request returns the original handle and conflicting reuse returns ClientError::AlreadyExists.
use StartOptions;
use Serialize;
let handle = client
.start_typed
.await?
.handle;
routing_key and task_queue are the two targeting dimensions. routing_key steers the start to the owner of that key's shard, forwarding there when the node you dialled is not the owner. task_queue sets the queue this workflow's activities default to, recorded durably on the start so it survives replay and failover. Both are part of the idempotency identity: reusing a key with a different routing key or task queue returns ClientError::AlreadyExists instead of handing back the first workflow.
display_name is the operator-facing name for the workflow this start creates, recorded durably on the start as the aion.display_name search attribute. It is a label over the UUID identity, never an address: no SDK call resolves a workflow by name, no SDK helper composes name-filter → take-first → act, and two workflows may wear the same name. Omit it to start unnamed. It is trimmed before it is sent, because the server trims it before recording it; a present-but-blank name is refused with ClientError::InvalidArgument before any round trip, because the server refuses one too rather than reading it as unnamed — absence is the only way to say "no name".
You name the run you are starting, but the label reads back per workflow: the recorded attribute carries no run id, so every reader folds it over the whole history, last write wins. A continue-as-new successor of this run inherits the name, and describing any run of the workflow returns whatever name was recorded most recently.
A display name is deliberately not part of the idempotency fingerprint. The key answers "is this the same act?", and a label carries no identity, so two starts differing only in name are the same act and dedupe to one run rather than returning AlreadyExists. The name that did not get applied is reported rather than dropped: the replay returns the existing run wearing its existing name, and display_name_not_applied carries both sides. The report fires only when this call asked for a name the standing run does not wear — whether that run wears a different name or none at all. A call that asked for no name dropped nothing, so it is told nothing even when the standing run has a name of its own.
let outcome = client
.start_typed
.await?;
if let Some = &outcome.display_name_not_applied
let handle = outcome.handle;
Renaming an existing run is a console/HTTP action (POST /workflows/rename), not an SDK or CLI operation in this release.
signal
handle
.signal_typed
.await?;
query
Query results can be decoded into any serde::Deserialize type, and query arguments are carried to the workflow's registered handler. Pass the typed arguments the handler expects; &() serializes to the JSON null document, which is the canonical "no arguments" request a no-argument query answers.
use Deserialize;
use Duration;
let state: EchoState = handle.query_typed.await?;
list
use ListPage;
use WorkflowFilter;
let summaries = client
.list
.await?;
describe
description.summary.display_name is the workflow's current operator-facing name, or None when it is unnamed. It is projected off the recorded aion.display_name search attribute folded over the whole history, so it is the most recently recorded name for the workflow — not necessarily the one this run was started with.
let description = handle.describe.await?;
println!;
println!;
cancel
Cancellation is a cooperative request: success means the server accepted it, not that the workflow has already reached a terminal state.
handle.cancel.await?;
subscribe
subscribe returns a Stream<Item = Result<Event, ClientError>>. Transient disconnects are retried with the next per-workflow sequence number so delivered events are gap-free and duplicate-free; terminal failures are yielded as stream errors.
use StreamExt;
let mut events = handle.subscribe;
while let Some = events.next.await
Typed and raw payloads
Typed helpers (start_typed, signal_typed, query_typed, to_payload, from_payload) use JSON by default. For pre-serialized or non-JSON data, use the raw aion_core::Payload escape hatch with the raw operation variants:
use ;
let raw = new;
handle.signal.await?;
Branching on errors
Every operation returns the shared branchable taxonomy via ClientError.
Each variant carries an ErrorDetail with the server's human detail message
and, when the wire supplied one, the structured error_type discriminator.
use ClientError;
match handle..await