Skip to main content

actix_web/
thin_data.rs

1use std::any::type_name;
2
3#[expect(deprecated, reason = "preserve the public ready future type")]
4use actix_utils::future::{ready, Ready};
5
6use crate::{dev::Payload, error, FromRequest, HttpRequest};
7
8/// Application data wrapper and extractor for cheaply-cloned types.
9///
10/// Similar to the [`Data`] wrapper but for `Clone`/`Copy` types that are already an `Arc` internally,
11/// share state using some other means when cloned, or is otherwise static data that is very cheap
12/// to clone.
13///
14/// Unlike `Data`, this wrapper clones `T` during extraction. Therefore, it is the user's
15/// responsibility to ensure that clones of `T` do actually share the same state, otherwise state
16/// may be unexpectedly different across multiple requests.
17///
18/// Note that if your type is literally an `Arc<T>` then it's recommended to use the
19/// [`Data::from(arc)`][data_from_arc] conversion instead.
20///
21/// # Examples
22///
23/// ```
24/// use actix_web::{
25///     web::{self, ThinData},
26///     App, HttpResponse, Responder,
27/// };
28///
29/// // Use the `ThinData<T>` extractor to access a database connection pool.
30/// async fn index(ThinData(db_pool): ThinData<DbPool>) -> impl Responder {
31///     // database action ...
32///
33///     HttpResponse::Ok()
34/// }
35///
36/// # type DbPool = ();
37/// let db_pool = DbPool::default();
38///
39/// App::new()
40///     .app_data(ThinData(db_pool.clone()))
41///     .service(web::resource("/").get(index))
42/// # ;
43/// ```
44///
45/// [`Data`]: crate::web::Data
46/// [data_from_arc]: crate::web::Data#impl-From<Arc<T>>-for-Data<T>
47#[derive(Debug, Clone)]
48pub struct ThinData<T>(pub T);
49
50impl_more::impl_as_ref!(ThinData<T> => T);
51impl_more::impl_as_mut!(ThinData<T> => T);
52impl_more::impl_deref_and_mut!(<T> in ThinData<T> => T);
53
54#[expect(deprecated, reason = "preserve the public ready future type")]
55impl<T: Clone + 'static> FromRequest for ThinData<T> {
56    type Error = crate::Error;
57    type Future = Ready<Result<Self, Self::Error>>;
58
59    #[inline]
60    fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
61        ready(req.app_data::<Self>().cloned().ok_or_else(|| {
62            log::debug!(
63                "Failed to extract `ThinData<{}>` for `{}` handler. For the ThinData extractor to work \
64                correctly, wrap the data with `ThinData()` and pass it to `App::app_data()`. \
65                Ensure that types align in both the set and retrieve calls.",
66                type_name::<T>(),
67                req.match_name().unwrap_or(req.path())
68            );
69
70            error::ErrorInternalServerError(
71                "Requested application data is not configured correctly. \
72                View/enable debug logs for more details.",
73            )
74        }))
75    }
76}
77
78#[cfg(test)]
79mod tests {
80    use std::sync::{Arc, Mutex};
81
82    use super::*;
83    use crate::{
84        http::StatusCode,
85        test::{call_service, init_service, TestRequest},
86        web, App, HttpResponse,
87    };
88
89    type TestT = Arc<Mutex<u32>>;
90
91    #[actix_rt::test]
92    async fn thin_data() {
93        let test_data = TestT::default();
94
95        let app = init_service(App::new().app_data(ThinData(test_data.clone())).service(
96            web::resource("/").to(|td: ThinData<TestT>| {
97                *td.lock().unwrap() += 1;
98                HttpResponse::Ok()
99            }),
100        ))
101        .await;
102
103        for _ in 0..3 {
104            let req = TestRequest::default().to_request();
105            let resp = call_service(&app, req).await;
106            assert_eq!(resp.status(), StatusCode::OK);
107        }
108
109        assert_eq!(*test_data.lock().unwrap(), 3);
110    }
111
112    #[actix_rt::test]
113    async fn thin_data_missing() {
114        let app = init_service(
115            App::new().service(web::resource("/").to(|_: ThinData<u32>| HttpResponse::Ok())),
116        )
117        .await;
118
119        let req = TestRequest::default().to_request();
120        let resp = call_service(&app, req).await;
121        assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);
122    }
123}