apalis_core/task_fn/guide.rs
1//! # Creating task handlers
2//!
3//! This tutorial covers the basics of writing task handlers,
4//! and implementing custom argument extraction via [`FromRequest`].
5//!
6//! ## Introduction
7//!
8//! The first argument of any task function is the type `Args` which is tied to the backend's task type.
9//! 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`.
10//!
11//! A rule of thumb is to never store database models as task arguments.
12//!
13//! Instead of doing this:
14//! ```rust
15//! struct User {
16//! id: String,
17//! // other fields...
18//! }
19//! struct Email {
20//! user: User,
21//! subject: String,
22//! message: String,
23//! }
24//! ```
25//! Do this:
26//! ```
27//! struct Email {
28//! user_id: String,
29//! subject: String,
30//! message: String,
31//! }
32//! ```
33//!
34//! All the primitive types (e.g. `String`, `u32`) can be used directly as task arguments.
35//!
36//! **Note:**
37//!
38//! > *Some backends like `apalis-cron` offer a specific `Args` type (Tick) for cron jobs while most others like `postgres` use a more generic `Args` type.*
39//!
40//! A guide for extracting complex information from tasks using [`FromRequest`] is available in [step 3](#3-implementing-custom-argument-extraction-with-fromrequest).
41//!
42//! ## 1. Writing basic task handlers
43//!
44//! Task handlers are async functions that process a task. You can use the [`task_fn`] helper
45//! to wrap your handler into a service.
46//!
47//! ```rust
48//! # use apalis_core::task::data::Data;
49//! #[derive(Clone)]
50//! struct State;
51//!
52//! // A simple handler that takes an id and injected state
53//! async fn handler(id: u32, state: Data<State>) -> String {
54//! format!("Got id {} with state", id)
55//! }
56//! ```
57//! You would need to inject the state in your worker builder:
58//!
59//! ```rs
60//! let worker = WorkerBuilder::new()
61//! .backend(in_memory)
62//! .data(State)
63//! .build(handler);
64//! ```
65//!
66//! ## 2. Dependency Injection in handlers
67//!
68//! `apalis-core` supports default injection for common types in your handler arguments, such as:
69//! - [`Attempt`]: Information about the current attempt
70//! - [`WorkerContext`]: Worker context
71//! - [`Data<T>`]: Injected data/state
72//! - [`TaskId`]: The unique ID of the task
73//!
74//! Example:
75//! ```rust
76//! # use apalis_core::task::{attempt::Attempt, data::Data, task_id::TaskId};
77//! #[derive(Clone)]
78//! struct State;
79//!
80//! async fn process_task(_: u32, attempt: Attempt, state: Data<State>, id: TaskId) -> String {
81//! format!("Attempt {} for task {} with state", attempt.current(), id)
82//! }
83//! ```
84//!
85//!
86//! ## 3. Implementing custom argument extraction with [`FromRequest`]
87//!
88//! You can extract custom types from the request by implementing [`FromRequest`].
89//!
90//! Suppose you have a task to send emails, and you want to automatically extract a `User` from the task's `user_id`:
91//!
92//! ```rust
93//! struct Email {
94//! user_id: String,
95//! subject: String,
96//! message: String,
97//! }
98//!
99//! // Implement FromRequest for User
100//! # use apalis_core::task_fn::from_request::FromRequest;
101//! # use apalis_core::task::Task;
102//! # use apalis_core::error::BoxDynError;
103//! # struct User {
104//! # id: String,
105//! # // other fields...
106//! # }
107//!
108//! impl<Ctx: Sync> FromRequest<Task<Email, Ctx>> for User {
109//! type Error = BoxDynError;
110//! async fn from_request(req: &Task<Email, Ctx>) -> Result<Self, Self::Error> {
111//! let user_id = req.args.user_id.clone();
112//! // Simulate fetching user from DB
113//! Ok(User { id: user_id })
114//! }
115//! }
116//!
117//! // Now your handler can take User directly
118//! async fn send_email(email: Email, user: User) -> Result<(), BoxDynError> {
119//! // Use email and user
120//! Ok(())
121//! }
122//! ```
123//!
124//! ## 4. How It Works
125//!
126//! - [`task_fn`] wraps your handler into a [`TaskFn`] service.
127//! - Arguments are extracted using [`FromRequest`].
128//! - DI types are injected automatically.
129//! - The handler's output is converted to a response using [`IntoResponse`].
130//!
131//! [`task_fn`]: crate::task_fn::task_fn
132//! [`TaskFn`]: crate::task_fn::TaskFn
133//! [`FromRequest`]: crate::task_fn::from_request::FromRequest
134//! [`IntoResponse`]: crate::task_fn::into_response::IntoResponse
135//! [`Attempt`]: crate::task::attempt::Attempt
136//! [`Data<T>`]: crate::task::data::Data
137//! [`WorkerContext`]: crate::worker::context::WorkerContext
138//! [`TaskId`]: crate::task::task_id::TaskId