actix_web_lab/
local_data.rs1use std::{any::type_name, ops::Deref, rc::Rc};
2
3use actix_utils::future::{err, ok, Ready};
4use actix_web::{dev::Payload, error, Error, FromRequest, HttpRequest};
5use tracing::debug;
6
7#[doc(alias = "state")]
9#[derive(Debug)]
10pub struct LocalData<T: ?Sized>(Rc<T>);
11
12impl<T> LocalData<T> {
13 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 dev::Service,
68 http::StatusCode,
69 test::{init_service, TestRequest},
70 web, App, HttpResponse,
71 };
72
73 use super::*;
74
75 trait TestTrait {
76 fn get_num(&self) -> i32;
77 }
78
79 struct A {}
80
81 impl TestTrait for A {
82 fn get_num(&self) -> i32 {
83 42
84 }
85 }
86
87 #[actix_web::test]
88 async fn test_app_data_extractor() {
89 let srv = init_service(
90 App::new()
91 .app_data(LocalData::new(10usize))
92 .service(web::resource("/").to(|_: LocalData<usize>| HttpResponse::Ok())),
93 )
94 .await;
95
96 let req = TestRequest::default().to_request();
97 let resp = srv.call(req).await.unwrap();
98 assert_eq!(resp.status(), StatusCode::OK);
99
100 let srv = init_service(
101 App::new()
102 .app_data(LocalData::new(10u32))
103 .service(web::resource("/").to(|_: LocalData<usize>| HttpResponse::Ok())),
104 )
105 .await;
106 let req = TestRequest::default().to_request();
107 let resp = srv.call(req).await.unwrap();
108 assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);
109 }
110
111 #[actix_web::test]
112 async fn test_override_data() {
113 let srv = init_service(
114 App::new().app_data(LocalData::new(1usize)).service(
115 web::resource("/")
116 .app_data(LocalData::new(10usize))
117 .route(web::get().to(|data: LocalData<usize>| {
118 assert_eq!(*data, 10);
119 HttpResponse::Ok()
120 })),
121 ),
122 )
123 .await;
124
125 let req = TestRequest::default().to_request();
126 let resp = srv.call(req).await.unwrap();
127 assert_eq!(resp.status(), StatusCode::OK);
128 }
129
130 #[actix_web::test]
131 async fn test_data_from_rc() {
132 let data_new = LocalData::new(String::from("test-123"));
133 let data_from_rc = LocalData::from(Rc::new(String::from("test-123")));
134 assert_eq!(data_new.0, data_from_rc.0);
135 }
136
137 #[actix_web::test]
138 async fn test_data_from_dyn_rc() {
139 let dyn_rc_box: Rc<Box<dyn TestTrait>> = Rc::new(Box::new(A {}));
141 let data_arc_box = LocalData::from(dyn_rc_box);
142
143 let dyn_rc: Rc<dyn TestTrait> = Rc::new(A {});
145 let data_arc = LocalData::from(dyn_rc);
146 assert_eq!(data_arc_box.get_num(), data_arc.get_num())
147 }
148
149 #[actix_web::test]
150 async fn test_get_ref_from_dyn_data() {
151 let dyn_rc: Rc<dyn TestTrait> = Rc::new(A {});
152 let data_arc = LocalData::from(dyn_rc);
153 let ref_data: &dyn TestTrait = &*data_arc;
154 assert_eq!(data_arc.get_num(), ref_data.get_num())
155 }
156}