Skip to main content

apalis_core/task/
builder.rs

1//! # Task Builder
2//!
3//! The `TaskBuilder` module provides a flexible builder pattern for constructing [`Task`] instances
4//! with customizable configuration options. It allows users to specify arguments, context, extensions,
5//! task identifiers, attempt information, status, and scheduling details for tasks.
6//!
7//! ## Features
8//! - Create tasks with required arguments and optional context.
9//! - Attach custom extensions/data to tasks.
10//! - Assign unique task identifiers.
11//! - Configure attempt and status information.
12//! - Schedule tasks to run at specific times, after delays, or at intervals (seconds, minutes, hours).
13//! - Build tasks with sensible defaults for omitted fields.
14//!
15//! ## Usage
16//! Use [`TaskBuilder`] to incrementally configure a task, then call `.build()` to obtain a [`Task`] instance.
17//! Convenience methods are provided for common scheduling scenarios.
18//!
19//! ### Example
20//! ```rust,ignore
21//! let task = TaskBuilder::new(args)
22//!     .max_attempts(3)
23//!     .run_in_minutes(10)
24//!     .build();
25//! ```
26//!
27use crate::{
28    backend::queue::Queue,
29    task::{
30        ExecutionContext, Task,
31        attempt::Attempt,
32        extensions::Extensions,
33        metadata::{Metadata, MetadataStore},
34        status::Status,
35        task_id::TaskId,
36    },
37};
38use std::{
39    fmt::Debug,
40    sync::Arc,
41    time::{Duration, SystemTime, UNIX_EPOCH},
42};
43
44/// Builder for creating [`Task`] instances with optional configuration
45#[derive(Debug)]
46#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
47#[must_use = "TaskBuilder is used to construct a Task. Ensure to call `.build()` to create the Task instance."]
48pub struct TaskBuilder<Args> {
49    /// The arguments for the task
50    pub(super) args: Args,
51    /// Execution context for the task, including metadata and extensions
52    pub(super) ctx: ExecutionContext,
53}
54
55impl<Args> TaskBuilder<Args> {
56    /// Create a new TaskBuilder with the required args
57    pub fn new(args: Args) -> Self {
58        Self {
59            args,
60            ctx: ExecutionContext::default(),
61        }
62    }
63
64    /// Set the task's metadata
65    pub fn with_metadata(mut self, metadata: MetadataStore) -> Self {
66        self.ctx.metadata = metadata;
67        self
68    }
69
70    /// Set the task's runtime data
71    pub fn with_data(mut self, data: Extensions) -> Self {
72        self.ctx.data = data;
73        self
74    }
75
76    /// Insert a value into the task's data context
77    pub fn data<D: Clone + Send + Sync + 'static>(mut self, value: D) -> Self {
78        self.ctx.data.insert(value);
79        self
80    }
81
82    /// Insert a value into the task's metadata
83    pub fn metadata<M>(mut self, value: &M) -> Self
84    where
85        M: Metadata,
86        M::Error: Debug,
87    {
88        value
89            .inject(&mut self.ctx.metadata)
90            .expect("Could not add Metadata");
91        self
92    }
93
94    /// Set the task ID
95    pub fn task_id(mut self, task_id: TaskId) -> Self {
96        self.ctx.task_id = Some(task_id);
97        self
98    }
99
100    /// Set the attempt information
101    pub fn attempt(mut self, attempt: usize) -> Self {
102        self.ctx.attempt = Attempt::new_with_value(attempt);
103        self
104    }
105
106    /// Set the task status
107    pub fn status(mut self, status: Status) -> Self {
108        self.ctx.status = status.into();
109        self
110    }
111
112    /// Set the maximum number of attempts allowed for the task
113    pub fn max_attempts(mut self, max_attempts: usize) -> Self {
114        self.ctx.max_attempts = Some(max_attempts);
115        self
116    }
117
118    /// Set the priority of the task
119    pub fn priority(mut self, priority: usize) -> Self {
120        self.ctx.priority = Some(priority);
121        self
122    }
123
124    /// Set the queue this task belongs to
125    pub fn queue(mut self, queue: Queue) -> Self {
126        self.ctx.queue = Some(queue);
127        self
128    }
129
130    /// Schedule the task to run at a specific Unix timestamp
131    pub fn run_at_timestamp(mut self, timestamp: u64) -> Self {
132        self.ctx.run_at = Some(timestamp);
133        self
134    }
135
136    /// Schedule the task to run at a specific SystemTime
137    pub fn run_at_time(mut self, time: SystemTime) -> Self {
138        let timestamp = time
139            .duration_since(UNIX_EPOCH)
140            .expect("Time went backwards")
141            .as_secs();
142        self.ctx.run_at = Some(timestamp);
143        self
144    }
145
146    /// Schedule the task to run after a delay from now
147    pub fn run_after(mut self, delay: Duration) -> Self {
148        let now = SystemTime::now();
149        let run_time = now + delay;
150        let timestamp = run_time
151            .duration_since(UNIX_EPOCH)
152            .expect("Time went backwards")
153            .as_secs();
154        self.ctx.run_at = Some(timestamp);
155        self
156    }
157
158    /// Schedule the task to run in the specified number of seconds
159    pub fn run_in_seconds(self, seconds: u64) -> Self {
160        self.run_after(Duration::from_secs(seconds))
161    }
162
163    /// Schedule the task to run in the specified number of minutes
164    pub fn run_in_minutes(self, minutes: u64) -> Self {
165        self.run_after(Duration::from_secs(minutes * 60))
166    }
167
168    /// Schedule the task to run in the specified number of hours
169    pub fn run_in_hours(self, hours: u64) -> Self {
170        self.run_after(Duration::from_secs(hours * 3600))
171    }
172
173    /// Set the idempotency key
174    pub fn idempotency_key<S: AsRef<str>>(mut self, idempotency_key: S) -> Self {
175        self.ctx.idempotency_key = Some(idempotency_key.as_ref().to_owned());
176        self
177    }
178
179    /// Set the time the task was completed
180    pub fn done_at(mut self, done_at: Option<u64>) -> Self {
181        self.ctx.done_at = done_at;
182        self
183    }
184
185    /// Set the time the task was locked
186    pub fn lock_at(mut self, lock_at: Option<u64>) -> Self {
187        self.ctx.lock_at = lock_at;
188        self
189    }
190
191    /// Set the worker/process identifier holding the lock on this task
192    pub fn lock_by(mut self, lock_by: Option<String>) -> Self {
193        self.ctx.lock_by = lock_by;
194        self
195    }
196
197    /// Build the Task with default context
198    pub fn build(self) -> Task<Args> {
199        Task {
200            args: self.args,
201            ctx: Arc::new(self.ctx),
202        }
203    }
204}
205
206impl<Args> TaskBuilder<Args> {
207    /// Maps the `args` field using the provided function, returning a result.
208    pub fn try_map_args<F, NewArgs, Err>(self, f: F) -> Result<TaskBuilder<NewArgs>, Err>
209    where
210        F: FnOnce(Args) -> Result<NewArgs, Err>,
211    {
212        Ok(TaskBuilder {
213            args: f(self.args)?,
214            ctx: self.ctx,
215        })
216    }
217    /// Maps the `args` field using the provided function, returning the new args.
218    pub fn map_args<F, NewArgs>(self, f: F) -> TaskBuilder<NewArgs>
219    where
220        F: FnOnce(Args) -> NewArgs,
221    {
222        TaskBuilder {
223            args: f(self.args),
224            ctx: self.ctx,
225        }
226    }
227
228    /// Maps only the `execution_context` field.
229    pub fn map_context<F>(self, f: F) -> Self
230    where
231        F: FnOnce(ExecutionContext) -> ExecutionContext,
232    {
233        Self {
234            args: self.args,
235            ctx: f(self.ctx),
236        }
237    }
238}