diff --git a/CHANGES.md b/CHANGES.md index a2071310..dcefdec5 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,10 +2,15 @@ ## [1.0.3] - unreleased +### Added + +* Support asynchronous data factories #850 + ### Changed * Use `encoding_rs` crate instead of unmaintained `encoding` crate + ## [1.0.2] - 2019-06-17 ### Changed @@ -17,7 +22,7 @@ ## [1.0.1] - 2019-06-17 -### Add +### Added * Add support for PathConfig #903 @@ -42,7 +47,7 @@ ## [1.0.0] - 2019-06-05 -### Add +### Added * Add `Scope::configure()` method. diff --git a/src/app.rs b/src/app.rs index 897b3645..3b063a84 100644 --- a/src/app.rs +++ b/src/app.rs @@ -8,7 +8,7 @@ use actix_service::boxed::{self, BoxedNewService}; use actix_service::{ apply_transform, IntoNewService, IntoTransform, NewService, Transform, }; -use futures::IntoFuture; +use futures::{Future, IntoFuture}; use crate::app_service::{AppEntry, AppInit, AppRoutingFactory}; use crate::config::{AppConfig, AppConfigInner, ServiceConfig}; @@ -23,6 +23,7 @@ use crate::service::{ }; type HttpNewService = BoxedNewService<(), ServiceRequest, ServiceResponse, Error, ()>; +type FnDataFactory = Box Box, Error = ()>>>; /// Application builder - structure that follows the builder pattern /// for building application instances. @@ -32,6 +33,7 @@ pub struct App { default: Option>, factory_ref: Rc>>, data: Vec>, + data_factories: Vec, config: AppConfigInner, external: Vec, _t: PhantomData<(B)>, @@ -44,6 +46,7 @@ impl App { App { endpoint: AppEntry::new(fref.clone()), data: Vec::new(), + data_factories: Vec::new(), services: Vec::new(), default: None, factory_ref: fref, @@ -100,6 +103,31 @@ where self } + /// Set application data factory. This function is + /// similar to `.data()` but it accepts data factory. Data object get + /// constructed asynchronously during application initialization. + pub fn data_factory(mut self, data: F) -> Self + where + F: Fn() -> Out + 'static, + Out: IntoFuture + 'static, + Out::Error: std::fmt::Debug, + { + self.data_factories.push(Box::new(move || { + Box::new( + data() + .into_future() + .map_err(|e| { + log::error!("Can not construct data instance: {:?}", e); + }) + .map(|data| { + let data: Box = Box::new(Data::new(data)); + data + }), + ) + })); + self + } + /// Set application data. Application data could be accessed /// by using `Data` extractor where `T` is data type. pub fn register_data(mut self, data: Data) -> Self { @@ -349,6 +377,7 @@ where App { endpoint, data: self.data, + data_factories: self.data_factories, services: self.services, default: self.default, factory_ref: self.factory_ref, @@ -423,6 +452,7 @@ where fn into_new_service(self) -> AppInit { AppInit { data: Rc::new(self.data), + data_factories: Rc::new(self.data_factories), endpoint: self.endpoint, services: Rc::new(RefCell::new(self.services)), external: RefCell::new(self.external), @@ -490,24 +520,24 @@ mod tests { assert_eq!(resp.status(), StatusCode::CREATED); } - // #[test] - // fn test_data_factory() { - // let mut srv = - // init_service(App::new().data_factory(|| Ok::<_, ()>(10usize)).service( - // web::resource("/").to(|_: web::Data| HttpResponse::Ok()), - // )); - // let req = TestRequest::default().to_request(); - // let resp = block_on(srv.call(req)).unwrap(); - // assert_eq!(resp.status(), StatusCode::OK); + #[test] + fn test_data_factory() { + let mut srv = + init_service(App::new().data_factory(|| Ok::<_, ()>(10usize)).service( + web::resource("/").to(|_: web::Data| HttpResponse::Ok()), + )); + let req = TestRequest::default().to_request(); + let resp = block_on(srv.call(req)).unwrap(); + assert_eq!(resp.status(), StatusCode::OK); - // let mut srv = - // init_service(App::new().data_factory(|| Ok::<_, ()>(10u32)).service( - // web::resource("/").to(|_: web::Data| HttpResponse::Ok()), - // )); - // let req = TestRequest::default().to_request(); - // let resp = block_on(srv.call(req)).unwrap(); - // assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR); - // } + let mut srv = + init_service(App::new().data_factory(|| Ok::<_, ()>(10u32)).service( + web::resource("/").to(|_: web::Data| HttpResponse::Ok()), + )); + let req = TestRequest::default().to_request(); + let resp = block_on(srv.call(req)).unwrap(); + assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR); + } fn md( req: ServiceRequest, diff --git a/src/app_service.rs b/src/app_service.rs index 5a9731bf..8ab9b352 100644 --- a/src/app_service.rs +++ b/src/app_service.rs @@ -25,6 +25,7 @@ type BoxedResponse = Either< FutureResult, Box>, >; +type FnDataFactory = Box Box, Error = ()>>>; /// Service factory to convert `Request` to a `ServiceRequest`. /// It also executes data factories. @@ -40,6 +41,7 @@ where { pub(crate) endpoint: T, pub(crate) data: Rc>>, + pub(crate) data_factories: Rc>, pub(crate) config: RefCell, pub(crate) services: Rc>>>, pub(crate) default: Option>, @@ -119,16 +121,12 @@ where let rmap = Rc::new(rmap); rmap.finish(rmap.clone()); - // create app data container - let mut data = Extensions::new(); - for f in self.data.iter() { - f.create(&mut data); - } - AppInitResult { endpoint: None, endpoint_fut: self.endpoint.new_service(&()), - data: Rc::new(data), + data: self.data.clone(), + data_factories: Vec::new(), + data_factories_fut: self.data_factories.iter().map(|f| f()).collect(), config, rmap, _t: PhantomData, @@ -144,7 +142,9 @@ where endpoint_fut: T::Future, rmap: Rc, config: AppConfig, - data: Rc, + data: Rc>>, + data_factories: Vec>, + data_factories_fut: Vec, Error = ()>>>, _t: PhantomData, } @@ -159,21 +159,43 @@ where >, { type Item = AppInitService; - type Error = T::InitError; + type Error = (); fn poll(&mut self) -> Poll { + // async data factories + let mut idx = 0; + while idx < self.data_factories_fut.len() { + match self.data_factories_fut[idx].poll()? { + Async::Ready(f) => { + self.data_factories.push(f); + self.data_factories_fut.remove(idx); + } + Async::NotReady => idx += 1, + } + } + if self.endpoint.is_none() { if let Async::Ready(srv) = self.endpoint_fut.poll()? { self.endpoint = Some(srv); } } - if self.endpoint.is_some() { + if self.endpoint.is_some() && self.data_factories_fut.is_empty() { + // create app data container + let mut data = Extensions::new(); + for f in self.data.iter() { + f.create(&mut data); + } + + for f in &self.data_factories { + f.create(&mut data); + } + Ok(Async::Ready(AppInitService { service: self.endpoint.take().unwrap(), rmap: self.rmap.clone(), config: self.config.clone(), - data: self.data.clone(), + data: Rc::new(data), pool: HttpRequestPool::create(), })) } else {