Skip to content

Commit a431b73

Browse files
authored
feat: add ThinData wrapper (#3446)
1 parent 5be5382 commit a431b73

File tree

5 files changed

+130
-1
lines changed

5 files changed

+130
-1
lines changed

actix-web/CHANGES.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
## Unreleased
44

5+
### Added
6+
7+
- Add `web::ThinData` extractor.
8+
59
## 4.8.0
610

711
### Added

actix-web/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,7 @@ encoding_rs = "0.8"
151151
futures-core = { version = "0.3.17", default-features = false }
152152
futures-util = { version = "0.3.17", default-features = false }
153153
itoa = "1"
154+
impl-more = "0.1.4"
154155
language-tags = "0.3"
155156
log = "0.4"
156157
mime = "0.3"

actix-web/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ mod scope;
104104
mod server;
105105
mod service;
106106
pub mod test;
107+
mod thin_data;
107108
pub(crate) mod types;
108109
pub mod web;
109110

actix-web/src/thin_data.rs

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
use std::any::type_name;
2+
3+
use actix_utils::future::{ready, Ready};
4+
5+
use crate::{dev::Payload, error, FromRequest, HttpRequest};
6+
7+
/// Application data wrapper and extractor for cheaply-cloned types.
8+
///
9+
/// Similar to the [`Data`] wrapper but for `Clone`/`Copy` types that are already an `Arc` internally,
10+
/// share state using some other means when cloned, or is otherwise static data that is very cheap
11+
/// to clone.
12+
///
13+
/// Unlike `Data`, this wrapper clones `T` during extraction. Therefore, it is the user's
14+
/// responsibility to ensure that clones of `T` do actually share the same state, otherwise state
15+
/// may be unexpectedly different across multiple requests.
16+
///
17+
/// Note that if your type is literally an `Arc<T>` then it's recommended to use the
18+
/// [`Data::from(arc)`][data_from_arc] conversion instead.
19+
///
20+
/// # Examples
21+
///
22+
/// ```
23+
/// use actix_web::{
24+
/// web::{self, ThinData},
25+
/// App, HttpResponse, Responder,
26+
/// };
27+
///
28+
/// // Use the `ThinData<T>` extractor to access a database connection pool.
29+
/// async fn index(ThinData(db_pool): ThinData<DbPool>) -> impl Responder {
30+
/// // database action ...
31+
///
32+
/// HttpResponse::Ok()
33+
/// }
34+
///
35+
/// # type DbPool = ();
36+
/// let db_pool = DbPool::default();
37+
///
38+
/// App::new()
39+
/// .app_data(ThinData(db_pool.clone()))
40+
/// .service(web::resource("/").get(index))
41+
/// # ;
42+
/// ```
43+
///
44+
/// [`Data`]: crate::web::Data
45+
/// [data_from_arc]: crate::web::Data#impl-From<Arc<T>>-for-Data<T>
46+
#[derive(Debug, Clone)]
47+
pub struct ThinData<T>(pub T);
48+
49+
impl_more::impl_as_ref!(ThinData<T> => T);
50+
impl_more::impl_as_mut!(ThinData<T> => T);
51+
impl_more::impl_deref_and_mut!(<T> in ThinData<T> => T);
52+
53+
impl<T: Clone + 'static> FromRequest for ThinData<T> {
54+
type Error = crate::Error;
55+
type Future = Ready<Result<Self, Self::Error>>;
56+
57+
#[inline]
58+
fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
59+
ready(req.app_data::<Self>().cloned().ok_or_else(|| {
60+
log::debug!(
61+
"Failed to extract `ThinData<{}>` for `{}` handler. For the ThinData extractor to work \
62+
correctly, wrap the data with `ThinData()` and pass it to `App::app_data()`. \
63+
Ensure that types align in both the set and retrieve calls.",
64+
type_name::<T>(),
65+
req.match_name().unwrap_or(req.path())
66+
);
67+
68+
error::ErrorInternalServerError(
69+
"Requested application data is not configured correctly. \
70+
View/enable debug logs for more details.",
71+
)
72+
}))
73+
}
74+
}
75+
76+
#[cfg(test)]
77+
mod tests {
78+
use std::sync::{Arc, Mutex};
79+
80+
use super::*;
81+
use crate::{
82+
http::StatusCode,
83+
test::{call_service, init_service, TestRequest},
84+
web, App, HttpResponse,
85+
};
86+
87+
type TestT = Arc<Mutex<u32>>;
88+
89+
#[actix_rt::test]
90+
async fn thin_data() {
91+
let test_data = TestT::default();
92+
93+
let app = init_service(App::new().app_data(ThinData(test_data.clone())).service(
94+
web::resource("/").to(|td: ThinData<TestT>| {
95+
*td.lock().unwrap() += 1;
96+
HttpResponse::Ok()
97+
}),
98+
))
99+
.await;
100+
101+
for _ in 0..3 {
102+
let req = TestRequest::default().to_request();
103+
let resp = call_service(&app, req).await;
104+
assert_eq!(resp.status(), StatusCode::OK);
105+
}
106+
107+
assert_eq!(*test_data.lock().unwrap(), 3);
108+
}
109+
110+
#[actix_rt::test]
111+
async fn thin_data_missing() {
112+
let app = init_service(
113+
App::new().service(web::resource("/").to(|_: ThinData<u32>| HttpResponse::Ok())),
114+
)
115+
.await;
116+
117+
let req = TestRequest::default().to_request();
118+
let resp = call_service(&app, req).await;
119+
assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);
120+
}
121+
}

actix-web/src/web.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
//!
33
//! # Request Extractors
44
//! - [`Data`]: Application data item
5+
//! - [`ThinData`]: Cheap-to-clone application data item
56
//! - [`ReqData`]: Request-local data item
67
//! - [`Path`]: URL path parameters / dynamic segments
78
//! - [`Query`]: URL query parameters
@@ -22,7 +23,8 @@ use actix_router::IntoPatterns;
2223
pub use bytes::{Buf, BufMut, Bytes, BytesMut};
2324

2425
pub use crate::{
25-
config::ServiceConfig, data::Data, redirect::Redirect, request_data::ReqData, types::*,
26+
config::ServiceConfig, data::Data, redirect::Redirect, request_data::ReqData,
27+
thin_data::ThinData, types::*,
2628
};
2729
use crate::{
2830
error::BlockingError, http::Method, service::WebService, FromRequest, Handler, Resource,

0 commit comments

Comments
 (0)