mirror of
https://github.com/actix/actix-web.git
synced 2024-11-28 12:31:13 +00:00
feat: add ThinData wrapper (#3446)
This commit is contained in:
parent
5be53820f0
commit
a431b7356c
5 changed files with 130 additions and 1 deletions
|
@ -2,6 +2,10 @@
|
||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Add `web::ThinData` extractor.
|
||||||
|
|
||||||
## 4.8.0
|
## 4.8.0
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
|
@ -151,6 +151,7 @@ encoding_rs = "0.8"
|
||||||
futures-core = { version = "0.3.17", default-features = false }
|
futures-core = { version = "0.3.17", default-features = false }
|
||||||
futures-util = { version = "0.3.17", default-features = false }
|
futures-util = { version = "0.3.17", default-features = false }
|
||||||
itoa = "1"
|
itoa = "1"
|
||||||
|
impl-more = "0.1.4"
|
||||||
language-tags = "0.3"
|
language-tags = "0.3"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
mime = "0.3"
|
mime = "0.3"
|
||||||
|
|
|
@ -104,6 +104,7 @@ mod scope;
|
||||||
mod server;
|
mod server;
|
||||||
mod service;
|
mod service;
|
||||||
pub mod test;
|
pub mod test;
|
||||||
|
mod thin_data;
|
||||||
pub(crate) mod types;
|
pub(crate) mod types;
|
||||||
pub mod web;
|
pub mod web;
|
||||||
|
|
||||||
|
|
121
actix-web/src/thin_data.rs
Normal file
121
actix-web/src/thin_data.rs
Normal file
|
@ -0,0 +1,121 @@
|
||||||
|
use std::any::type_name;
|
||||||
|
|
||||||
|
use actix_utils::future::{ready, Ready};
|
||||||
|
|
||||||
|
use crate::{dev::Payload, error, FromRequest, HttpRequest};
|
||||||
|
|
||||||
|
/// Application data wrapper and extractor for cheaply-cloned types.
|
||||||
|
///
|
||||||
|
/// Similar to the [`Data`] wrapper but for `Clone`/`Copy` types that are already an `Arc` internally,
|
||||||
|
/// share state using some other means when cloned, or is otherwise static data that is very cheap
|
||||||
|
/// to clone.
|
||||||
|
///
|
||||||
|
/// Unlike `Data`, this wrapper clones `T` during extraction. Therefore, it is the user's
|
||||||
|
/// responsibility to ensure that clones of `T` do actually share the same state, otherwise state
|
||||||
|
/// may be unexpectedly different across multiple requests.
|
||||||
|
///
|
||||||
|
/// Note that if your type is literally an `Arc<T>` then it's recommended to use the
|
||||||
|
/// [`Data::from(arc)`][data_from_arc] conversion instead.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use actix_web::{
|
||||||
|
/// web::{self, ThinData},
|
||||||
|
/// App, HttpResponse, Responder,
|
||||||
|
/// };
|
||||||
|
///
|
||||||
|
/// // Use the `ThinData<T>` extractor to access a database connection pool.
|
||||||
|
/// async fn index(ThinData(db_pool): ThinData<DbPool>) -> impl Responder {
|
||||||
|
/// // database action ...
|
||||||
|
///
|
||||||
|
/// HttpResponse::Ok()
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// # type DbPool = ();
|
||||||
|
/// let db_pool = DbPool::default();
|
||||||
|
///
|
||||||
|
/// App::new()
|
||||||
|
/// .app_data(ThinData(db_pool.clone()))
|
||||||
|
/// .service(web::resource("/").get(index))
|
||||||
|
/// # ;
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// [`Data`]: crate::web::Data
|
||||||
|
/// [data_from_arc]: crate::web::Data#impl-From<Arc<T>>-for-Data<T>
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct ThinData<T>(pub T);
|
||||||
|
|
||||||
|
impl_more::impl_as_ref!(ThinData<T> => T);
|
||||||
|
impl_more::impl_as_mut!(ThinData<T> => T);
|
||||||
|
impl_more::impl_deref_and_mut!(<T> in ThinData<T> => T);
|
||||||
|
|
||||||
|
impl<T: Clone + 'static> FromRequest for ThinData<T> {
|
||||||
|
type Error = crate::Error;
|
||||||
|
type Future = Ready<Result<Self, Self::Error>>;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
|
||||||
|
ready(req.app_data::<Self>().cloned().ok_or_else(|| {
|
||||||
|
log::debug!(
|
||||||
|
"Failed to extract `ThinData<{}>` for `{}` handler. For the ThinData extractor to work \
|
||||||
|
correctly, wrap the data with `ThinData()` and pass it to `App::app_data()`. \
|
||||||
|
Ensure that types align in both the set and retrieve calls.",
|
||||||
|
type_name::<T>(),
|
||||||
|
req.match_name().unwrap_or(req.path())
|
||||||
|
);
|
||||||
|
|
||||||
|
error::ErrorInternalServerError(
|
||||||
|
"Requested application data is not configured correctly. \
|
||||||
|
View/enable debug logs for more details.",
|
||||||
|
)
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
use crate::{
|
||||||
|
http::StatusCode,
|
||||||
|
test::{call_service, init_service, TestRequest},
|
||||||
|
web, App, HttpResponse,
|
||||||
|
};
|
||||||
|
|
||||||
|
type TestT = Arc<Mutex<u32>>;
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn thin_data() {
|
||||||
|
let test_data = TestT::default();
|
||||||
|
|
||||||
|
let app = init_service(App::new().app_data(ThinData(test_data.clone())).service(
|
||||||
|
web::resource("/").to(|td: ThinData<TestT>| {
|
||||||
|
*td.lock().unwrap() += 1;
|
||||||
|
HttpResponse::Ok()
|
||||||
|
}),
|
||||||
|
))
|
||||||
|
.await;
|
||||||
|
|
||||||
|
for _ in 0..3 {
|
||||||
|
let req = TestRequest::default().to_request();
|
||||||
|
let resp = call_service(&app, req).await;
|
||||||
|
assert_eq!(resp.status(), StatusCode::OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_eq!(*test_data.lock().unwrap(), 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn thin_data_missing() {
|
||||||
|
let app = init_service(
|
||||||
|
App::new().service(web::resource("/").to(|_: ThinData<u32>| HttpResponse::Ok())),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let req = TestRequest::default().to_request();
|
||||||
|
let resp = call_service(&app, req).await;
|
||||||
|
assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,6 +2,7 @@
|
||||||
//!
|
//!
|
||||||
//! # Request Extractors
|
//! # Request Extractors
|
||||||
//! - [`Data`]: Application data item
|
//! - [`Data`]: Application data item
|
||||||
|
//! - [`ThinData`]: Cheap-to-clone application data item
|
||||||
//! - [`ReqData`]: Request-local data item
|
//! - [`ReqData`]: Request-local data item
|
||||||
//! - [`Path`]: URL path parameters / dynamic segments
|
//! - [`Path`]: URL path parameters / dynamic segments
|
||||||
//! - [`Query`]: URL query parameters
|
//! - [`Query`]: URL query parameters
|
||||||
|
@ -22,7 +23,8 @@ use actix_router::IntoPatterns;
|
||||||
pub use bytes::{Buf, BufMut, Bytes, BytesMut};
|
pub use bytes::{Buf, BufMut, Bytes, BytesMut};
|
||||||
|
|
||||||
pub use crate::{
|
pub use crate::{
|
||||||
config::ServiceConfig, data::Data, redirect::Redirect, request_data::ReqData, types::*,
|
config::ServiceConfig, data::Data, redirect::Redirect, request_data::ReqData,
|
||||||
|
thin_data::ThinData, types::*,
|
||||||
};
|
};
|
||||||
use crate::{
|
use crate::{
|
||||||
error::BlockingError, http::Method, service::WebService, FromRequest, Handler, Resource,
|
error::BlockingError, http::Method, service::WebService, FromRequest, Handler, Resource,
|
||||||
|
|
Loading…
Reference in a new issue