Write durable functions in Rust via the Inngest SDK.
Read the documentation and get started in minutes.
Inngest’s SDK adds durable functions to Rust in a few lines of code. Using this SDK, you can write background jobs, and workflows
as step functions without the need to setup queueing infrastructure.
We currently support the following web frameworks:
If there are other frameworks you like to see, feel free to submit an issue, or add to the roadmap.
| Features | Support |
|---|---|
step.run |
:white_check_mark: |
step.sleep |
:white_check_mark: |
step.wait_for_event |
:white_check_mark: |
step.invoke |
:white_check_mark: |
step.send_event |
:white_check_mark: |
| CancelOn | :white_check_mark: |
| Failure handler | :white_check_mark: |
| Retry controls | :white_check_mark: |
| Middleware | :x: |
| Connect | :x: |
[dependencies]
inngest = "0.1"
use axum::{
routing::get,
Router
};
use tokio::net::TcpListener;
#[tokio::main]
async fn main() {
let client = Inngest::new("rust-app");
let mut inngest_handler = Handler::new(&client);
inngest_handler.register_fns(vec![
hello_fn(&client).into(),
step_run_fn(&client).into(),
]);
let inngest_state = Arc::new(inngest_handler);
let app = Router::new()
.route("/", get(|| async { "OK!\n" }))
.route(
"/api/inngest",
get(serve::axum::introspect)
.put(serve::axum::register)
.post(serve::axum::invoke),
)
.with_state(inngest_state);
let listener = TcpListener::bind("[::]:3000").await.unwrap();
axum::serve(listener, app.into_make_service())
.await
.unwrap();
}
#[derive(Serialize, Deserialize, Debug)]
struct HelloEventData {
msg: String,
}
#[derive(Serialize, Deserialize, Debug)]
struct StepRunEventData {
name: String,
data: u8,
}
fn hello_fn(client: &Inngest) -> ServableFn<HelloEventData, Error> {
client.create_function(
FunctionOpts::new("hello-func").name("Hello func"),
Trigger::event("test/hello"),
|input: Input<HelloEventData>, step: StepTool| async move {
let step_res = into_dev_result!(
step.run("fallible-step-function", || async move {
// if even, fail
if input.ctx.attempt % 2 == 0 {
return Err(UserLandError::General(format!(
"Attempt {}",
input.ctx.attempt
)));
}
Ok(json!({ "returned from within step.run": true }))
}).await
)?;
step.sleep("sleep-test", Duration::from_secs(3))?;
let evt: Option<Event<Value>> = step.wait_for_event(
"wait",
WaitForEventOpts {
event: "test/wait".to_string(),
timeout: Duration::from_secs(60),
if_exp: None,
},
)?;
Ok(json!({ "hello": true, "evt": evt, "step": step_res }))
},
)
}
fn step_run_fn(client: &Inngest) -> ServableFn<StepRunEventData, Error> {
client.create_function(
FunctionOpts::new("step-run").name("Step run"),
Trigger::event("test/step-run"),
|_input: Input<StepRunEventData>, _step: StepTool| async move {
Ok(json!({ "step": true }))
},
)
}
The handler can register functions with different event payload types on the same app. When batching them with register_fns(...), convert each function with .into() as shown above.
Function definitions also support sync metadata such as cancel, idempotency, batch_events, rate_limit, debounce, priority, concurrency, throttle, singleton, and timeouts.
Failure handlers use a Rust-style on_failure(...) method on the returned ServableFn:
fn hello_with_failure_fn(client: &Inngest) -> ServableFn<HelloEventData, Error> {
client
.create_function(
FunctionOpts::new("hello-func"),
Trigger::event("test/hello"),
|input: Input<HelloEventData>, _step: StepTool| async move {
Ok(json!({ "hello": input.event.data.msg }))
},
)
.on_failure(
|input: Input<FunctionFailureEvent<HelloEventData>>, _step: StepTool| async move {
Ok(json!({
"failed_run_id": input.event.data.run_id,
"message": input.event.data.error.message,
"original": input.event.data.event.data.msg,
}))
},
)
}