Skip to main content

actix_web_lab/
local_data.rs

1use std::{
2    any::type_name,
3    future::{Ready, ready},
4    ops::Deref,
5    rc::Rc,
6};
7
8use actix_web::{Error, FromRequest, HttpRequest, dev::Payload, error};
9use tracing::debug;
10
11/// A thread-local equivalent to [`SharedData`](crate::extract::SharedData).
12#[doc(alias = "state")]
13#[derive(Debug)]
14pub struct LocalData<T: ?Sized>(Rc<T>);
15
16impl<T> LocalData<T> {
17    /// Constructs a new `LocalData` instance.
18    pub fn new(item: T) -> LocalData<T> {
19        LocalData(Rc::new(item))
20    }
21}
22
23impl<T: ?Sized> Deref for LocalData<T> {
24    type Target = T;
25
26    fn deref(&self) -> &T {
27        &self.0
28    }
29}
30
31impl<T: ?Sized> Clone for LocalData<T> {
32    fn clone(&self) -> LocalData<T> {
33        LocalData(Rc::clone(&self.0))
34    }
35}
36
37impl<T: ?Sized> From<Rc<T>> for LocalData<T> {
38    fn from(rc: Rc<T>) -> Self {
39        LocalData(rc)
40    }
41}
42
43impl<T: ?Sized + 'static> FromRequest for LocalData<T> {
44    type Error = Error;
45    type Future = Ready<Result<Self, Error>>;
46
47    #[inline]
48    fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
49        if let Some(st) = req.app_data::<LocalData<T>>() {
50            ready(Ok(st.clone()))
51        } else {
52            debug!(
53                "Failed to extract `LocalData<{}>` for `{}` handler. For the LocalData extractor \
54                to work correctly, wrap the data with `LocalData::new()` and pass it to \
55                `App::app_data()`. Ensure that types align in both the set and retrieve calls.",
56                type_name::<T>(),
57                req.match_name().unwrap_or_else(|| req.path())
58            );
59
60            ready(Err(error::ErrorInternalServerError(
61                "Requested application data is not configured correctly. \
62                View/enable debug logs for more details.",
63            )))
64        }
65    }
66}
67
68#[cfg(test)]
69mod tests {
70    use actix_web::{
71        App, HttpResponse,
72        dev::Service,
73        http::StatusCode,
74        test::{TestRequest, init_service},
75        web,
76    };
77
78    use super::*;
79
80    trait TestTrait {
81        fn get_num(&self) -> i32;
82    }
83
84    struct A {}
85
86    impl TestTrait for A {
87        fn get_num(&self) -> i32 {
88            42
89        }
90    }
91
92    #[actix_web::test]
93    async fn test_app_data_extractor() {
94        let srv = init_service(
95            App::new()
96                .app_data(LocalData::new(10usize))
97                .service(web::resource("/").to(|_: LocalData<usize>| HttpResponse::Ok())),
98        )
99        .await;
100
101        let req = TestRequest::default().to_request();
102        let resp = srv.call(req).await.unwrap();
103        assert_eq!(resp.status(), StatusCode::OK);
104
105        let srv = init_service(
106            App::new()
107                .app_data(LocalData::new(10u32))
108                .service(web::resource("/").to(|_: LocalData<usize>| HttpResponse::Ok())),
109        )
110        .await;
111        let req = TestRequest::default().to_request();
112        let resp = srv.call(req).await.unwrap();
113        assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);
114    }
115
116    #[actix_web::test]
117    async fn test_override_data() {
118        let srv = init_service(
119            App::new().app_data(LocalData::new(1usize)).service(
120                web::resource("/")
121                    .app_data(LocalData::new(10usize))
122                    .route(web::get().to(|data: LocalData<usize>| {
123                        assert_eq!(*data, 10);
124                        HttpResponse::Ok()
125                    })),
126            ),
127        )
128        .await;
129
130        let req = TestRequest::default().to_request();
131        let resp = srv.call(req).await.unwrap();
132        assert_eq!(resp.status(), StatusCode::OK);
133    }
134
135    #[actix_web::test]
136    async fn test_data_from_rc() {
137        let data_new = LocalData::new(String::from("test-123"));
138        let data_from_rc = LocalData::from(Rc::new(String::from("test-123")));
139        assert_eq!(data_new.0, data_from_rc.0);
140    }
141
142    #[actix_web::test]
143    async fn test_data_from_dyn_rc() {
144        // This works when Sized is required
145        let dyn_rc_box: Rc<Box<dyn TestTrait>> = Rc::new(Box::new(A {}));
146        let data_arc_box = LocalData::from(dyn_rc_box);
147
148        // This works when Data Sized Bound is removed
149        let dyn_rc: Rc<dyn TestTrait> = Rc::new(A {});
150        let data_arc = LocalData::from(dyn_rc);
151        assert_eq!(data_arc_box.get_num(), data_arc.get_num())
152    }
153
154    #[actix_web::test]
155    async fn test_get_ref_from_dyn_data() {
156        let dyn_rc: Rc<dyn TestTrait> = Rc::new(A {});
157        let data_arc = LocalData::from(dyn_rc);
158        let ref_data: &dyn TestTrait = &*data_arc;
159        assert_eq!(data_arc.get_num(), ref_data.get_num())
160    }
161}