actix_web_lab/
swap_data.rs

1use 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/// A wrapper around `ArcSwap` that can be used as an extractor.
9///
10/// Can serve as a replacement for `Data<RwLock<T>>` in certain situations.
11///
12/// Currently exposes some internals of `arc-swap` and may change in the future.
13#[derive(Debug)]
14pub struct SwapData<T> {
15    swap: Arc<ArcSwap<T>>,
16}
17
18impl<T: Send + Sync> SwapData<T> {
19    /// Constructs new swappable data item.
20    pub fn new(item: T) -> Self {
21        Self {
22            swap: Arc::new(ArcSwap::new(Arc::new(item))),
23        }
24    }
25
26    /// Returns a temporary access guard to the wrapped data item.
27    ///
28    /// Implements `Deref` for read access to the inner data item.
29    pub fn load(&self) -> Guard<Arc<T>> {
30        self.swap.load()
31    }
32
33    /// Replaces the value inside this instance.
34    ///
35    /// Further `load`s will yield the new value.
36    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        // first load in handler loads initial value
115        let extracted_data = SwapData::<NonCopy>::extract(&req).await.unwrap();
116        assert_eq!(**extracted_data.load(), NonCopy(42));
117
118        // change data
119        extracted_data.store(NonCopy(80));
120
121        // next load in handler loads new value
122        let extracted_data = SwapData::<NonCopy>::extract(&req).await.unwrap();
123        assert_eq!(**extracted_data.load(), NonCopy(80));
124
125        // initial extracted data stays the same
126        assert_eq!(*initial_data, NonCopy(42));
127    }
128}