apalis_core/task/
metadata.rs

1//! Task metadata extension trait and implementations
2//!
3//! The [`MetadataExt`] trait allows injecting and extracting metadata associated with tasks.
4//! It includes implementations for common metadata types.
5//!
6//! ## Overview
7//! - `MetadataExt<T>`: A trait for extracting and injecting metadata of type `T`.
8//!
9//! # Usage
10//! Implement the `MetadataExt` trait for your metadata types to enable easy extraction and injection
11//! from task contexts. This allows middleware and services to access and modify task metadata in a
12//! type-safe manner.
13use crate::task::Task;
14use crate::task_fn::FromRequest;
15use std::ops::Deref;
16
17/// Metadata wrapper for task contexts.
18#[derive(Debug, Clone)]
19pub struct Meta<T>(pub T);
20
21impl<T> Deref for Meta<T> {
22    type Target = T;
23    fn deref(&self) -> &Self::Target {
24        &self.0
25    }
26}
27
28/// Task metadata extension trait and implementations.
29/// This trait allows for injecting and extracting metadata associated with tasks.
30pub trait MetadataExt<T> {
31    /// The error type that can occur during extraction or injection.
32    type Error;
33    /// Extract metadata of type `T`.
34    fn extract(&self) -> Result<T, Self::Error>;
35    /// Inject metadata of type `T`.
36    fn inject(&mut self, value: T) -> Result<(), Self::Error>;
37}
38
39impl<T, Args: Send + Sync, Ctx: MetadataExt<T> + Send + Sync, IdType: Send + Sync>
40    FromRequest<Task<Args, Ctx, IdType>> for Meta<T>
41{
42    type Error = Ctx::Error;
43
44    async fn from_request(task: &Task<Args, Ctx, IdType>) -> Result<Self, Self::Error> {
45        task.parts.ctx.extract().map(Meta)
46    }
47}
48
49#[cfg(test)]
50#[allow(unused)]
51mod tests {
52    use std::{convert::Infallible, fmt::Debug, task::Poll, time::Duration};
53
54    use crate::{
55        error::BoxDynError,
56        task::{
57            Task,
58            metadata::{Meta, MetadataExt},
59        },
60        task_fn::FromRequest,
61    };
62    use futures_core::future::BoxFuture;
63    use tower::Service;
64
65    #[derive(Debug, Clone)]
66    struct ExampleService<S> {
67        service: S,
68    }
69    #[derive(Debug, Clone, Default)]
70    struct ExampleConfig {
71        timeout: Duration,
72    }
73
74    struct SampleStore;
75
76    impl MetadataExt<ExampleConfig> for SampleStore {
77        type Error = Infallible;
78        fn extract(&self) -> Result<ExampleConfig, Self::Error> {
79            Ok(ExampleConfig {
80                timeout: Duration::from_secs(1),
81            })
82        }
83        fn inject(&mut self, _: ExampleConfig) -> Result<(), Self::Error> {
84            unreachable!()
85        }
86    }
87
88    impl<S, Args: Send + Sync + 'static, Ctx: Send + Sync + 'static, IdType: Send + Sync + 'static>
89        Service<Task<Args, Ctx, IdType>> for ExampleService<S>
90    where
91        S: Service<Task<Args, Ctx, IdType>> + Clone + Send + 'static,
92        Ctx: MetadataExt<ExampleConfig> + Send,
93        Ctx::Error: Debug,
94        S::Future: Send + 'static,
95    {
96        type Response = S::Response;
97        type Error = S::Error;
98        type Future = BoxFuture<'static, Result<Self::Response, Self::Error>>;
99
100        fn poll_ready(&mut self, cx: &mut std::task::Context<'_>) -> Poll<Result<(), Self::Error>> {
101            self.service.poll_ready(cx)
102        }
103
104        fn call(&mut self, request: Task<Args, Ctx, IdType>) -> Self::Future {
105            let mut svc = self.service.clone();
106
107            // Do something with config
108            Box::pin(async move {
109                let _config: Meta<ExampleConfig> = request.extract().await.unwrap();
110                svc.call(request).await
111            })
112        }
113    }
114}