Expand description
Utilities for adapting async functions into a task handler.
The task_fn helper and the TaskFn struct in this module allow you to wrap
async functions or closures into a TaskFn implementation, which can then be
used in service middleware pipelines or other components expecting a TaskFn.
This is particularly useful when building lightweight, composable services from plain
functions, including those with extracted arguments via FromRequest.
§Features
- Supports functions with up to 16 additional arguments beyond the core request.
- Automatically applies argument extraction using the
FromRequesttrait. - Converts output to responses using the
IntoResponsetrait. - Captures function argument types at compile time via generics for static dispatch.
§Introduction
The first argument of any task function is the type Args which is tied to the backend’s task type.
Eg if you are writing a task for an email service, Args might be a struct Email that includes fields like user_id, subject, and message.
A rule of thumb is to never store database models as task arguments.
Instead of doing this:
struct User {
id: String,
// other fields...
}
struct Email {
user: User,
subject: String,
message: String,
}Do this:
struct Email {
user_id: String,
subject: String,
message: String,
}All the primitive types (e.g. String, u32) can be used directly as task arguments.
Note:
Some backends like
apalis-cronoffer a specificArgstype (Tick) for cron jobs while most others likepostgresuse a more genericArgstype.
A guide for extracting complex information from tasks using FromRequest is available in step 3.
§Getting started
Task handlers are async functions that process a task. You can use the task_fn helper
to wrap your handler into a service.
#[derive(Clone)]
struct State;
// A simple handler that takes an id and injected state
async fn handler(id: u32, state: Data<State>) -> String {
format!("Got id {} with state", id)
}You would need to inject the state in your worker builder:
let worker = WorkerBuilder::new()
.backend(in_memory)
.data(State)
.build(handler);§Dependency Injection
apalis-core supports default injection for common types in your handler arguments, such as:
WorkerContext: Worker contextTaskContext: The tasks context including the execution contextAttempt: Information about the current attemptData<T>: Injected data/stateTaskId: The unique ID of the task
Example:
#[derive(Clone)]
struct State;
async fn process_task(_: u32, attempt: Attempt, ctx: TaskContext, id: TaskId) -> String {
format!("Attempt {} for task {} with elapsed: {:?}", attempt.current(), id, ctx.elapsed())
}§Custom argument extraction with FromRequest
You can extract custom types from the request by implementing FromRequest.
Suppose you have a task to send emails, and you want to automatically extract a User from the task’s user_id:
struct Email {
user_id: String,
subject: String,
message: String,
}
// Implement FromRequest for User
impl FromRequest<Task<Email>> for User {
type Error = BoxDynError;
async fn from_request(req: &Task<Email>) -> Result<Self, Self::Error> {
let user_id = req.args.user_id.clone();
// Simulate fetching user from DB
Ok(User { id: user_id })
}
}
// Now your handler can take User directly
async fn send_email(email: Email, user: User) -> Result<(), BoxDynError> {
// Use email and user
Ok(())
}§How It Works
task_fnwraps your handler into aTaskFnservice.- Arguments are extracted using
FromRequest. - DI types are injected automatically.
- The handler’s output is converted to a response using
IntoResponse.
Structs§
- TaskFn
- An executable service implemented by a closure.