diff --git a/CHANGES.md b/CHANGES.md index ce7da6d28..aa42900d0 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,11 @@ # Changes +## [1.0.0-beta.4] - 2019-05-xx + +### Add + +* Allow to set/override app data on scope level + ### Changes * `App::configure` take an `FnOnce` instead of `Fn` diff --git a/src/app.rs b/src/app.rs index cc04630a3..1568d5fca 100644 --- a/src/app.rs +++ b/src/app.rs @@ -95,8 +95,8 @@ where /// web::get().to(index))); /// } /// ``` - pub fn data> + 'static>(mut self, data: U) -> Self { - self.data.push(Box::new(data.into())); + pub fn data(mut self, data: U) -> Self { + self.data.push(Box::new(Data::new(data))); self } diff --git a/src/config.rs b/src/config.rs index 62fd01be6..bc33da9df 100644 --- a/src/config.rs +++ b/src/config.rs @@ -31,7 +31,7 @@ pub struct AppService { Option, Option>, )>, - route_data: Rc>>, + service_data: Rc>>, } impl AppService { @@ -39,12 +39,12 @@ impl AppService { pub(crate) fn new( config: AppConfig, default: Rc, - route_data: Rc>>, + service_data: Rc>>, ) -> Self { AppService { config, default, - route_data, + service_data, root: true, services: Vec::new(), } @@ -75,7 +75,7 @@ impl AppService { default: self.default.clone(), services: Vec::new(), root: false, - route_data: self.route_data.clone(), + service_data: self.service_data.clone(), } } @@ -90,11 +90,11 @@ impl AppService { } /// Set global route data - pub fn set_route_data(&self, extensions: &mut Extensions) -> bool { - for f in self.route_data.iter() { + pub fn set_service_data(&self, extensions: &mut Extensions) -> bool { + for f in self.service_data.iter() { f.create(extensions); } - !self.route_data.is_empty() + !self.service_data.is_empty() } /// Register http service @@ -192,8 +192,8 @@ impl ServiceConfig { /// by using `Data` extractor where `T` is data type. /// /// This is same as `App::data()` method. - pub fn data> + 'static>(&mut self, data: S) -> &mut Self { - self.data.push(Box::new(data.into())); + pub fn data(&mut self, data: S) -> &mut Self { + self.data.push(Box::new(Data::new(data))); self } diff --git a/src/data.rs b/src/data.rs index f23bfff7f..5cb636f6a 100644 --- a/src/data.rs +++ b/src/data.rs @@ -93,12 +93,6 @@ impl Clone for Data { } } -impl From for Data { - fn from(data: T) -> Self { - Data::new(data) - } -} - impl FromRequest for Data { type Config = (); type Error = Error; @@ -135,6 +129,7 @@ impl DataFactory for Data { #[cfg(test)] mod tests { use actix_service::Service; + use std::sync::Mutex; use crate::http::StatusCode; use crate::test::{block_on, init_service, TestRequest}; diff --git a/src/resource.rs b/src/resource.rs index 2040a1bbe..7f76e0f5c 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -407,7 +407,7 @@ where } // custom app data storage if let Some(ref mut ext) = self.data { - config.set_route_data(ext); + config.set_service_data(ext); } config.register_service(rdef, guards, self, None) } diff --git a/src/scope.rs b/src/scope.rs index 59d3c6738..84f34dae6 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -2,7 +2,7 @@ use std::cell::RefCell; use std::fmt; use std::rc::Rc; -use actix_http::Response; +use actix_http::{Extensions, Response}; use actix_router::{ResourceDef, ResourceInfo, Router}; use actix_service::boxed::{self, BoxedNewService, BoxedService}; use actix_service::{ @@ -11,6 +11,7 @@ use actix_service::{ use futures::future::{ok, Either, Future, FutureResult}; use futures::{Async, IntoFuture, Poll}; +use crate::data::Data; use crate::dev::{AppService, HttpServiceFactory}; use crate::error::Error; use crate::guard::Guard; @@ -61,6 +62,7 @@ type BoxedResponse = Either< pub struct Scope { endpoint: T, rdef: String, + data: Option, services: Vec>, guards: Vec>, default: Rc>>>, @@ -74,6 +76,7 @@ impl Scope { Scope { endpoint: ScopeEndpoint::new(fref.clone()), rdef: path.to_string(), + data: None, guards: Vec::new(), services: Vec::new(), default: Rc::new(RefCell::new(None)), @@ -117,6 +120,39 @@ where self } + /// Set or override application data. Application data could be accessed + /// by using `Data` extractor where `T` is data type. + /// + /// ```rust + /// use std::cell::Cell; + /// use actix_web::{web, App}; + /// + /// struct MyData { + /// counter: Cell, + /// } + /// + /// fn index(data: web::Data) { + /// data.counter.set(data.counter.get() + 1); + /// } + /// + /// fn main() { + /// let app = App::new().service( + /// web::scope("/app") + /// .data(MyData{ counter: Cell::new(0) }) + /// .service( + /// web::resource("/index.html").route( + /// web::get().to(index))) + /// ); + /// } + /// ``` + pub fn data(mut self, data: U) -> Self { + if self.data.is_none() { + self.data = Some(Extensions::new()); + } + self.data.as_mut().unwrap().insert(Data::new(data)); + self + } + /// Register http service. /// /// This is similar to `App's` service registration. @@ -241,6 +277,7 @@ where Scope { endpoint, rdef: self.rdef, + data: self.data, guards: self.guards, services: self.services, default: self.default, @@ -308,7 +345,7 @@ where InitError = (), > + 'static, { - fn register(self, config: &mut AppService) { + fn register(mut self, config: &mut AppService) { // update default resource if needed if self.default.borrow().is_none() { *self.default.borrow_mut() = Some(config.default_service()); @@ -322,8 +359,14 @@ where let mut rmap = ResourceMap::new(ResourceDef::root_prefix(&self.rdef)); + // custom app data storage + if let Some(ref mut ext) = self.data { + config.set_service_data(ext); + } + // complete scope pipeline creation *self.factory_ref.borrow_mut() = Some(ScopeFactory { + data: self.data.take().map(|data| Rc::new(data)), default: self.default.clone(), services: Rc::new( cfg.into_services() @@ -355,6 +398,7 @@ where } pub struct ScopeFactory { + data: Option>, services: Rc>)>>, default: Rc>>>, } @@ -388,6 +432,7 @@ impl NewService for ScopeFactory { }) .collect(), default: None, + data: self.data.clone(), default_fut, } } @@ -397,6 +442,7 @@ impl NewService for ScopeFactory { #[doc(hidden)] pub struct ScopeFactoryResponse { fut: Vec, + data: Option>, default: Option, default_fut: Option>>, } @@ -460,6 +506,7 @@ impl Future for ScopeFactoryResponse { router }); Ok(Async::Ready(ScopeService { + data: self.data.clone(), router: router.finish(), default: self.default.take(), _ready: None, @@ -471,6 +518,7 @@ impl Future for ScopeFactoryResponse { } pub struct ScopeService { + data: Option>, router: Router>>, default: Option, _ready: Option<(ServiceRequest, ResourceInfo)>, @@ -499,6 +547,9 @@ impl Service for ScopeService { }); if let Some((srv, _info)) = res { + if let Some(ref data) = self.data { + req.set_data_container(data.clone()); + } Either::A(srv.call(req)) } else if let Some(ref mut default) = self.default { Either::A(default.call(req)) @@ -953,4 +1004,22 @@ mod tests { HeaderValue::from_static("0001") ); } + + #[test] + fn test_override_data() { + let mut srv = init_service(App::new().data(1usize).service( + web::scope("app").data(10usize).route( + "/t", + web::get().to(|data: web::Data| { + assert_eq!(*data, 10); + let _ = data.clone(); + HttpResponse::Ok() + }), + ), + )); + + let req = TestRequest::with_uri("/app/t").to_request(); + let resp = call_service(&mut srv, req); + assert_eq!(resp.status(), StatusCode::OK); + } }