actix_web_lab/
local_data.rs

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