Skip to main content
Current documentVerified

First unit — Rust

SDK 0.9.0 requires Rust ≥1.85; the example uses edition 2024. Host and types come from separate crates. Runtime requires Dapr, Control Plane, OIDC and a link key.

Registry and project​

Set the administrator-provided index in .cargo/config.toml. Keep the appropriate read token outside the project in ~/.cargo/credentials.toml under the same registry name. The value needs a Bearer prefix; Cargo sends it as the Authorization header.

# .cargo/config.toml
[registries.gitea]
index = "sparse+https://registry.example.com/cargo/"
credential-provider = "cargo:token"
# ~/.cargo/credentials.toml
[registries.gitea]
token = "Bearer YOUR_READ_TOKEN"
[package]
name = "cat-unit"
version = "0.1.0"
edition = "2024"
rust-version = "1.85"

[dependencies]
seeraos-unit-sdk = { version = "0.9.0", registry = "gitea" }
seeraos-unit-sdk-types = { version = "0.9.0", registry = "gitea" }
tokio = { version = "1", features = ["macros", "rt-multi-thread", "signal"] }

Exposition and program​

Save expositions/cat.v1.json and src/main.rs. Import UnitError and PublishedDocument from seeraos_unit_sdk_types; the host does not re-export them. The document is a release artifact and must match the bytes exposed through parity.

{
"contract": "cat.v1",
"unit": "catapi",
"objects": {
"Cat": {
"identity": "id",
"fields": { "id": { "type": "string" }, "name": { "type": "string" } }
}
},
"operations": {
"cat.get": { "object": "Cat", "verb": "get", "scopes": ["cat:read"] }
}
}
use seeraos_unit_sdk::{
Enrollment, FileIdentityStore, OperationCall, OperationReply, TokenVerifier, UnitHost,
};
use seeraos_unit_sdk_types::{ErrorType, PublishedDocument, UnitError};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let host = UnitHost::builder()
.document(PublishedDocument::new(include_str!("../expositions/cat.v1.json"))?)
.verify_tokens(
TokenVerifier::builder(std::env::var("SEERAOS_AUTHORITY")?)
.audience("seeraos")
.build()?,
)
.handle("cat.get", |call: OperationCall| async move {
let Some(id) = call.entity_id else {
return Err(UnitError::new(
ErrorType::NotFound,
"Cat.NotFound",
"A cat is read by id.",
));
};
OperationReply::json(format!(r#"{{"id":"{id}"}}"#)).map_err(|error| {
UnitError::new(ErrorType::Failure, "Cat.ReplyUnformed", error.to_string())
})
})
.build()?
.bind("0.0.0.0:8080".parse()?)
.await?;

let enrollment = Enrollment::builder(
std::env::var("SEERAOS_CONTROL_PLANE_URL")?,
FileIdentityStore::new("/var/lib/unit/identity"),
)
.link_key(std::env::var("SEERAOS_LINK_KEY")?)
.build()?;

host.run(enrollment, shutdown()).await?;
Ok(())
}

async fn shutdown() {
let _ = tokio::signal::ctrl_c().await;
}

Running and order​

Set Control Plane and OIDC addresses in the program variables, the issued link key and a persistent volume for /var/lib/unit/identity. Start Dapr according to your environment configuration; the unit process listens on port 8080.

cargo run

The listener starts before enrollment: Control Plane reads parity during linking. The host restores identity or saves a new identity after first enrollment. It then retrieves configuration, calls the callback and reports state. Until apply succeeds, operations answer Tenant.NotReady.

For configuration, combine config_fields with on_configure. For event publication, obtain the instance through on_enrolled and use its publisher within the document declarations. Add consumers through EventConsumer and consumes declarations; publishedIn selects the subscription location. The cat read example implements no emitter or consumer.

The example copies response formatting from the source tutorial; in your own handler, serialize arbitrary user data with a proper JSON serializer. Do not build JSON by interpolating untrusted strings.

HostError::IssuerRequired points to verify_tokens, HostError::UndeclaredConfiguration to field declarations, and HostError::NoRuntime to tokio. Missing handlers carry Operation.HandlerMissing or Event.HandlerMissing. Troubleshooting explains further steps. Volume and credential provider selection depend on the deployment.