Skip to main content

actix_web_lab/
swap_data.rs

1use std::{
2    future::{Ready, ready},
3    sync::Arc,
4};
5
6use actix_web::{Error, FromRequest, HttpRequest, dev, error};
7use arc_swap::{ArcSwap, Guard};
8use tracing::debug;
9
10/// A wrapper around `ArcSwap` that can be used as an extractor.
11///
12/// Can serve as a replacement for `Data<RwLock<T>>` in certain situations.
13///
14/// Currently exposes some internals of `arc-swap` and may change in the future.
15#[derive(Debug)]
16pub struct SwapData<T> {
17    swap: Arc<ArcSwap<T>>,
18}
19
20impl<T: Send + Sync> SwapData<T> {
21    /// Constructs new swappable data item.
22    pub fn new(item: T) -> Self {
23        Self {
24            swap: Arc::new(ArcSwap::new(Arc::new(item))),
25        }
26    }
27
28    /// Returns a temporary access guard to the wrapped data item.
29    ///
30    /// Implements `Deref` for read access to the inner data item.
31    pub fn load(&self) -> Guard<Arc<T>> {
32        self.swap.load()
33    }
34
35    /// Replaces the value inside this instance.
36    ///
37    /// Further `load`s will yield the new value.
38    pub fn store(&self, item: T) {
39        self.swap.store(Arc::new(item))
40    }
41}
42
43impl<T> Clone for SwapData<T> {
44    fn clone(&self) -> Self {
45        Self {
46            swap: Arc::clone(&self.swap),
47        }
48    }
49}
50
51impl<T: 'static> FromRequest for SwapData<T> {
52    type Error = Error;
53    type Future = Ready<Result<Self, Self::Error>>;
54
55    fn from_request(req: &HttpRequest, _pl: &mut dev::Payload) -> Self::Future {
56        if let Some(data) = req.app_data::<SwapData<T>>() {
57            ready(Ok(SwapData {
58                swap: Arc::clone(&data.swap),
59            }))
60        } else {
61            debug!(
62                "Failed to extract `SwapData<{}>` for `{}` handler. For the Data extractor to work \
63                correctly, wrap the data with `SwapData::new()` and pass it to `App::app_data()`. \
64                Ensure that types align in both the set and retrieve calls.",
65                core::any::type_name::<T>(),
66                req.match_name().unwrap_or_else(|| req.path())
67            );
68
69            ready(Err(error::ErrorInternalServerError(
70                "Requested application data is not configured correctly. \
71                View/enable debug logs for more details.",
72            )))
73        }
74    }
75}
76
77#[cfg(test)]
78mod tests {
79    use actix_web::test::TestRequest;
80
81    use super::*;
82
83    #[derive(Debug, Clone, PartialEq, Eq)]
84    struct NonCopy(u32);
85
86    #[actix_web::test]
87    async fn deref() {
88        let data = SwapData::new(NonCopy(42));
89        let inner_data = data.load();
90        let _inner_data: &NonCopy = &inner_data;
91    }
92
93    #[actix_web::test]
94    async fn extract_success() {
95        let data = SwapData::new(NonCopy(42));
96
97        let req = TestRequest::default().app_data(data).to_http_request();
98        let extracted_data = SwapData::<NonCopy>::extract(&req).await.unwrap();
99
100        assert_eq!(**extracted_data.load(), NonCopy(42));
101    }
102
103    #[actix_web::test]
104    async fn extract_fail() {
105        let req = TestRequest::default().to_http_request();
106        SwapData::<()>::extract(&req).await.unwrap_err();
107    }
108
109    #[actix_web::test]
110    async fn store_and_reload() {
111        let data = SwapData::new(NonCopy(42));
112        let initial_data = Guard::into_inner(data.load());
113
114        let req = TestRequest::default().app_data(data).to_http_request();
115
116        // first load in handler loads initial value
117        let extracted_data = SwapData::<NonCopy>::extract(&req).await.unwrap();
118        assert_eq!(**extracted_data.load(), NonCopy(42));
119
120        // change data
121        extracted_data.store(NonCopy(80));
122
123        // next load in handler loads new value
124        let extracted_data = SwapData::<NonCopy>::extract(&req).await.unwrap();
125        assert_eq!(**extracted_data.load(), NonCopy(80));
126
127        // initial extracted data stays the same
128        assert_eq!(*initial_data, NonCopy(42));
129    }
130}