actix_web_lab/
swap_data.rs1use std::sync::Arc;
2
3use actix_utils::future::{Ready, ready};
4use actix_web::{Error, FromRequest, HttpRequest, dev, error};
5use arc_swap::{ArcSwap, Guard};
6use tracing::debug;
7
8#[derive(Debug)]
14pub struct SwapData<T> {
15 swap: Arc<ArcSwap<T>>,
16}
17
18impl<T: Send + Sync> SwapData<T> {
19 pub fn new(item: T) -> Self {
21 Self {
22 swap: Arc::new(ArcSwap::new(Arc::new(item))),
23 }
24 }
25
26 pub fn load(&self) -> Guard<Arc<T>> {
30 self.swap.load()
31 }
32
33 pub fn store(&self, item: T) {
37 self.swap.store(Arc::new(item))
38 }
39}
40
41impl<T> Clone for SwapData<T> {
42 fn clone(&self) -> Self {
43 Self {
44 swap: Arc::clone(&self.swap),
45 }
46 }
47}
48
49impl<T: 'static> FromRequest for SwapData<T> {
50 type Error = Error;
51 type Future = Ready<Result<Self, Self::Error>>;
52
53 fn from_request(req: &HttpRequest, _pl: &mut dev::Payload) -> Self::Future {
54 if let Some(data) = req.app_data::<SwapData<T>>() {
55 ready(Ok(SwapData {
56 swap: Arc::clone(&data.swap),
57 }))
58 } else {
59 debug!(
60 "Failed to extract `SwapData<{}>` for `{}` handler. For the Data extractor to work \
61 correctly, wrap the data with `SwapData::new()` and pass it to `App::app_data()`. \
62 Ensure that types align in both the set and retrieve calls.",
63 core::any::type_name::<T>(),
64 req.match_name().unwrap_or_else(|| req.path())
65 );
66
67 ready(Err(error::ErrorInternalServerError(
68 "Requested application data is not configured correctly. \
69 View/enable debug logs for more details.",
70 )))
71 }
72 }
73}
74
75#[cfg(test)]
76mod tests {
77 use actix_web::test::TestRequest;
78
79 use super::*;
80
81 #[derive(Debug, Clone, PartialEq, Eq)]
82 struct NonCopy(u32);
83
84 #[actix_web::test]
85 async fn deref() {
86 let data = SwapData::new(NonCopy(42));
87 let inner_data = data.load();
88 let _inner_data: &NonCopy = &inner_data;
89 }
90
91 #[actix_web::test]
92 async fn extract_success() {
93 let data = SwapData::new(NonCopy(42));
94
95 let req = TestRequest::default().app_data(data).to_http_request();
96 let extracted_data = SwapData::<NonCopy>::extract(&req).await.unwrap();
97
98 assert_eq!(**extracted_data.load(), NonCopy(42));
99 }
100
101 #[actix_web::test]
102 async fn extract_fail() {
103 let req = TestRequest::default().to_http_request();
104 SwapData::<()>::extract(&req).await.unwrap_err();
105 }
106
107 #[actix_web::test]
108 async fn store_and_reload() {
109 let data = SwapData::new(NonCopy(42));
110 let initial_data = Guard::into_inner(data.load());
111
112 let req = TestRequest::default().app_data(data).to_http_request();
113
114 let extracted_data = SwapData::<NonCopy>::extract(&req).await.unwrap();
116 assert_eq!(**extracted_data.load(), NonCopy(42));
117
118 extracted_data.store(NonCopy(80));
120
121 let extracted_data = SwapData::<NonCopy>::extract(&req).await.unwrap();
123 assert_eq!(**extracted_data.load(), NonCopy(80));
124
125 assert_eq!(*initial_data, NonCopy(42));
127 }
128}