use std::{fmt::Debug, ops::Deref, sync::Arc};
use crate::{
backend::queue::Queue,
task::{
attempt::Attempt,
builder::TaskBuilder,
extensions::Extensions,
from_request::FromRequest,
metadata::{Metadata, MetadataStore},
runs::Run,
status::{AtomicStatus, Status},
task_id::TaskId,
},
};
pub mod attempt;
pub mod builder;
#[cfg(feature = "task-context")]
pub mod context;
pub mod data;
pub mod extensions;
pub mod from_request;
pub mod into_response;
pub mod metadata;
pub mod runs;
pub mod status;
pub mod task_fn;
pub mod task_id;
#[derive(Debug, Clone, Default)]
pub struct Task<Args> {
pub args: Args,
ctx: Arc<ExecutionContext>,
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct ExecutionContext {
task_id: Option<TaskId>,
#[cfg_attr(feature = "serde", serde(skip))]
data: Extensions,
attempt: Attempt,
status: AtomicStatus,
run_at: Option<u64>,
done_at: Option<u64>,
lock_at: Option<u64>,
lock_by: Option<String>,
idempotency_key: Option<String>,
metadata: MetadataStore,
max_attempts: Option<usize>,
priority: Option<usize>,
queue: Option<Queue>,
runs: Vec<Run>,
}
impl ExecutionContext {
#[must_use]
pub fn task_id(&self) -> Option<&TaskId> {
self.task_id.as_ref()
}
#[must_use]
pub fn data(&self) -> &Extensions {
&self.data
}
#[must_use]
pub fn attempt(&self) -> usize {
self.attempt.current()
}
#[must_use]
pub fn raw_attempt(&self) -> &Attempt {
&self.attempt
}
#[must_use]
pub fn status(&self) -> Status {
self.status.load()
}
#[must_use]
pub fn raw_status(&self) -> &AtomicStatus {
&self.status
}
#[must_use]
pub fn run_at(&self) -> Option<u64> {
self.run_at
}
#[must_use]
pub fn done_at(&self) -> Option<u64> {
self.done_at
}
#[must_use]
pub fn lock_at(&self) -> Option<u64> {
self.lock_at
}
#[must_use]
pub fn lock_by(&self) -> Option<&str> {
self.lock_by.as_deref()
}
#[must_use]
pub fn idempotency_key(&self) -> Option<&str> {
self.idempotency_key.as_deref()
}
#[must_use]
pub fn metadata(&self) -> &MetadataStore {
&self.metadata
}
#[must_use]
pub fn max_attempts(&self) -> Option<usize> {
self.max_attempts
}
#[must_use]
pub fn priority(&self) -> Option<usize> {
self.priority
}
#[must_use]
pub fn queue(&self) -> Option<&Queue> {
self.queue.as_ref()
}
#[must_use]
pub fn runs(&self) -> &[Run] {
&self.runs
}
}
impl<Args> Task<Args> {
pub fn new(args: Args) -> Self {
Self {
args,
ctx: Default::default(),
}
}
#[must_use]
pub fn ctx(&self) -> &Arc<ExecutionContext> {
&self.ctx
}
}
impl Default for ExecutionContext {
fn default() -> Self {
Self {
task_id: None,
data: Extensions::default(),
attempt: Attempt::default(),
status: AtomicStatus::new(Status::Pending),
run_at: None,
done_at: None,
lock_at: None,
lock_by: None,
idempotency_key: None,
metadata: MetadataStore::default(),
max_attempts: None,
priority: None,
queue: None,
runs: Vec::new(),
}
}
}
impl Debug for ExecutionContext {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ExecutionContext")
.field("task_id", &self.task_id)
.field("data", &"<Extensions>")
.field("attempt", &self.attempt)
.field("status", &self.status.load())
.field("run_at", &self.run_at)
.field("done_at", &self.done_at)
.field("lock_at", &self.lock_at)
.field("lock_by", &self.lock_by)
.field("idempotency_key", &self.idempotency_key)
.field("metadata", &self.metadata)
.field("max_attempts", &self.max_attempts)
.field("runs", &self.runs)
.field("priority", &self.priority)
.field("queue", &self.queue)
.finish()
}
}
impl Clone for ExecutionContext {
fn clone(&self) -> Self {
Self {
task_id: self.task_id.clone(),
data: self.data.clone(),
attempt: self.attempt.clone(),
status: self.status.clone(),
run_at: self.run_at,
done_at: self.done_at,
lock_at: self.lock_at,
lock_by: self.lock_by.clone(),
idempotency_key: self.idempotency_key.clone(),
metadata: self.metadata.clone(),
runs: self.runs.clone(),
max_attempts: self.max_attempts,
priority: self.priority,
queue: self.queue.clone(),
}
}
}
impl<Args> Task<Args> {
#[must_use]
pub fn take(self) -> (Args, Arc<ExecutionContext>) {
(self.args, self.ctx)
}
#[must_use = "An extracted value should be used or handled to avoid unused value warnings."]
pub async fn extract<T: FromRequest<Self>>(&self) -> Result<T, T::Error> {
T::from_request(self).await
}
pub fn map_args<F, NewArgs>(self, f: F) -> Task<NewArgs>
where
F: FnOnce(Args) -> NewArgs,
{
Task {
args: f(self.args),
ctx: self.ctx,
}
}
#[must_use = "A mapped task should be used or handled to avoid unused value warnings."]
pub fn try_map_args<F, NewArgs, Err>(self, f: F) -> Result<Task<NewArgs>, Err>
where
F: FnOnce(Args) -> Result<NewArgs, Err>,
{
Ok(Task {
args: f(self.args)?,
ctx: self.ctx,
})
}
#[must_use]
pub fn map_context<F>(self, f: F) -> Self
where
F: FnOnce(Arc<ExecutionContext>) -> Arc<ExecutionContext>,
{
Self {
args: self.args,
ctx: f(self.ctx),
}
}
#[must_use]
#[doc(hidden)]
pub fn map_id_type(self) -> Self {
let mut ctx = Arc::unwrap_or_clone(self.ctx);
let _ = ctx.metadata.insert(
"apalis_core.transform.old_id",
ctx.task_id.map(|id| id.to_string()).unwrap_or_default(),
);
Self {
args: self.args,
ctx: Arc::new(ExecutionContext {
task_id: None,
data: ctx.data,
attempt: ctx.attempt,
status: ctx.status,
run_at: ctx.run_at,
done_at: ctx.done_at,
lock_at: ctx.lock_at,
lock_by: ctx.lock_by,
idempotency_key: ctx.idempotency_key,
metadata: ctx.metadata,
max_attempts: ctx.max_attempts,
priority: ctx.priority,
queue: ctx.queue,
runs: ctx.runs,
}),
}
}
#[must_use = "Converting a task into a builder allows for further modifications before rebuilding the task."]
pub fn into_builder(self) -> TaskBuilder<Args> {
TaskBuilder {
args: self.args,
ctx: Arc::unwrap_or_clone(self.ctx),
}
}
pub fn inject_data<D>(&mut self, data: D) -> Option<D>
where
D: Send + Clone + Sync + 'static,
{
let ctx = Arc::make_mut(&mut self.ctx);
ctx.data.insert(data)
}
pub fn inject_metadata<M>(&mut self, value: &M) -> Result<(), M::Error>
where
M: Metadata,
{
let ctx = Arc::make_mut(&mut self.ctx);
value.inject(&mut ctx.metadata)?;
Ok(())
}
#[doc(hidden)]
pub fn new_with_ctx(args: Args, ctx: Arc<ExecutionContext>) -> Self {
Self { args, ctx }
}
}
impl<Args> Deref for Task<Args> {
type Target = ExecutionContext;
fn deref(&self) -> &Self::Target {
&self.ctx
}
}
impl<A: Send + 'static> From<A> for Task<A> {
fn from(value: A) -> Self {
Self {
args: value,
ctx: Default::default(),
}
}
}