From 237bfba1ed2e7fd1e739093c24ab75fa01a8fac6 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 3 Apr 2019 15:09:31 -0700 Subject: [PATCH] add App::configure() - allow to offload app configuration to different methods --- CHANGES.md | 6 +- actix-multipart/README.md | 2 +- src/app.rs | 90 ++++++++++++++++++++++- src/config.rs | 146 +++++++++++++++++++++++++++++++++++++- src/web.rs | 1 + 5 files changed, 241 insertions(+), 4 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index fc690ee50..4aaef9511 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,10 @@ # Changes -## [1.0.0-alpha.3] - 2019-04-xx +## [1.0.0-alpha.4] - 2019-04-xx + +### Added + +* `App::configure()` allow to offload app configuration to different methods ### Changed diff --git a/actix-multipart/README.md b/actix-multipart/README.md index 2a65840a1..2739ff3d5 100644 --- a/actix-multipart/README.md +++ b/actix-multipart/README.md @@ -1 +1 @@ -# Multipart support for actix web framework [![Build Status](https://travis-ci.org/actix/actix-web.svg?branch=master)](https://travis-ci.org/actix/actix-web) [![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web) [![crates.io](https://meritbadge.herokuapp.com/actix-session)](https://crates.io/crates/actix-session) [![Join the chat at https://gitter.im/actix/actix](https://badges.gitter.im/actix/actix.svg)](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +# Multipart support for actix web framework [![Build Status](https://travis-ci.org/actix/actix-web.svg?branch=master)](https://travis-ci.org/actix/actix-web) [![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web) [![crates.io](https://meritbadge.herokuapp.com/actix-multipart)](https://crates.io/crates/actix-multipart) [![Join the chat at https://gitter.im/actix/actix](https://badges.gitter.im/actix/actix.svg)](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) diff --git a/src/app.rs b/src/app.rs index fd91d0728..802569458 100644 --- a/src/app.rs +++ b/src/app.rs @@ -15,7 +15,7 @@ use bytes::Bytes; use futures::{IntoFuture, Stream}; use crate::app_service::{AppChain, AppEntry, AppInit, AppRouting, AppRoutingFactory}; -use crate::config::{AppConfig, AppConfigInner}; +use crate::config::{AppConfig, AppConfigInner, RouterConfig}; use crate::data::{Data, DataFactory}; use crate::dev::{Payload, PayloadStream, ResourceDef}; use crate::error::{Error, PayloadError}; @@ -257,6 +257,55 @@ where } } + /// Run external configuration as part of the application building + /// process + /// + /// This function is useful for moving parts of configuration to a + /// different module or even library. For example, + /// some of the resource's configuration could be moved to different module. + /// + /// ```rust + /// # extern crate actix_web; + /// use actix_web::{web, middleware, App, HttpResponse}; + /// + /// // this function could be located in different module + /// fn config

(cfg: &mut web::RouterConfig

) { + /// cfg.service(web::resource("/test") + /// .route(web::get().to(|| HttpResponse::Ok())) + /// .route(web::head().to(|| HttpResponse::MethodNotAllowed())) + /// ); + /// } + /// + /// fn main() { + /// let app = App::new() + /// .wrap(middleware::Logger::default()) + /// .configure(config) // <- register resources + /// .route("/index.html", web::get().to(|| HttpResponse::Ok())); + /// } + /// ``` + pub fn configure(mut self, f: F) -> AppRouter> + where + F: Fn(&mut RouterConfig), + { + let mut cfg = RouterConfig::new(); + f(&mut cfg); + self.data.extend(cfg.data); + + let fref = Rc::new(RefCell::new(None)); + + AppRouter { + chain: self.chain, + default: None, + endpoint: AppEntry::new(fref.clone()), + factory_ref: fref, + data: self.data, + config: self.config, + services: cfg.services, + external: cfg.external, + _t: PhantomData, + } + } + /// Configure route for a specific path. /// /// This is a simplified version of the `App::service()` method. @@ -382,6 +431,45 @@ where InitError = (), >, { + /// Run external configuration as part of the application building + /// process + /// + /// This function is useful for moving parts of configuration to a + /// different module or even library. For example, + /// some of the resource's configuration could be moved to different module. + /// + /// ```rust + /// # extern crate actix_web; + /// use actix_web::{web, middleware, App, HttpResponse}; + /// + /// // this function could be located in different module + /// fn config

(cfg: &mut web::RouterConfig

) { + /// cfg.service(web::resource("/test") + /// .route(web::get().to(|| HttpResponse::Ok())) + /// .route(web::head().to(|| HttpResponse::MethodNotAllowed())) + /// ); + /// } + /// + /// fn main() { + /// let app = App::new() + /// .wrap(middleware::Logger::default()) + /// .configure(config) // <- register resources + /// .route("/index.html", web::get().to(|| HttpResponse::Ok())); + /// } + /// ``` + pub fn configure(mut self, f: F) -> Self + where + F: Fn(&mut RouterConfig

), + { + let mut cfg = RouterConfig::new(); + f(&mut cfg); + self.data.extend(cfg.data); + self.services.extend(cfg.services); + self.external.extend(cfg.external); + + self + } + /// Configure route for a specific path. /// /// This is a simplified version of the `App::service()` method. diff --git a/src/config.rs b/src/config.rs index ceb58feb7..1e552291f 100644 --- a/src/config.rs +++ b/src/config.rs @@ -5,11 +5,18 @@ use std::rc::Rc; use actix_http::Extensions; use actix_router::ResourceDef; use actix_service::{boxed, IntoNewService, NewService}; +use futures::IntoFuture; +use crate::data::{Data, DataFactory}; use crate::error::Error; use crate::guard::Guard; +use crate::resource::Resource; use crate::rmap::ResourceMap; -use crate::service::{ServiceRequest, ServiceResponse}; +use crate::route::Route; +use crate::service::{ + HttpServiceFactory, ServiceFactory, ServiceFactoryWrapper, ServiceRequest, + ServiceResponse, +}; type Guards = Vec>; type HttpNewService

= @@ -157,3 +164,140 @@ impl Default for AppConfigInner { } } } + +/// Router config. It is used for external configuration. +/// Part of application configuration could be offloaded +/// to set of external methods. This could help with +/// modularization of big application configuration. +pub struct RouterConfig { + pub(crate) services: Vec>>, + pub(crate) data: Vec>, + pub(crate) external: Vec, +} + +impl RouterConfig

{ + pub(crate) fn new() -> Self { + Self { + services: Vec::new(), + data: Vec::new(), + external: Vec::new(), + } + } + + /// Set application data. Applicatin data could be accessed + /// by using `Data` extractor where `T` is data type. + /// + /// This is same as `App::data()` method. + pub fn data(&mut self, data: S) -> &mut Self { + self.data.push(Box::new(Data::new(data))); + self + } + + /// Set application data factory. This function is + /// similar to `.data()` but it accepts data factory. Data object get + /// constructed asynchronously during application initialization. + /// + /// This is same as `App::data_dactory()` method. + pub fn data_factory(&mut self, data: F) -> &mut Self + where + F: Fn() -> R + 'static, + R: IntoFuture + 'static, + R::Error: std::fmt::Debug, + { + self.data.push(Box::new(data)); + self + } + + /// Configure route for a specific path. + /// + /// This is same as `App::route()` method. + pub fn route(&mut self, path: &str, mut route: Route

) -> &mut Self { + self.service( + Resource::new(path) + .add_guards(route.take_guards()) + .route(route), + ) + } + + /// Register http service. + /// + /// This is same as `App::service()` method. + pub fn service(&mut self, factory: F) -> &mut Self + where + F: HttpServiceFactory

+ 'static, + { + self.services + .push(Box::new(ServiceFactoryWrapper::new(factory))); + self + } + + /// Register an external resource. + /// + /// External resources are useful for URL generation purposes only + /// and are never considered for matching at request time. Calls to + /// `HttpRequest::url_for()` will work as expected. + /// + /// This is same as `App::external_service()` method. + pub fn external_resource(&mut self, name: N, url: U) -> &mut Self + where + N: AsRef, + U: AsRef, + { + let mut rdef = ResourceDef::new(url.as_ref()); + *rdef.name_mut() = name.as_ref().to_string(); + self.external.push(rdef); + self + } +} + +#[cfg(test)] +mod tests { + use actix_service::Service; + + use super::*; + use crate::http::StatusCode; + use crate::test::{block_on, init_service, TestRequest}; + use crate::{web, App, HttpResponse}; + + #[test] + fn test_data() { + let cfg = |cfg: &mut RouterConfig<_>| { + cfg.data(10usize); + }; + + let mut srv = + init_service(App::new().configure(cfg).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 cfg = |cfg: &mut RouterConfig<_>| { + cfg.data_factory(|| Ok::<_, ()>(10usize)); + }; + + let mut srv = + init_service(App::new().configure(cfg).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 cfg2 = |cfg: &mut RouterConfig<_>| { + cfg.data_factory(|| Ok::<_, ()>(10u32)); + }; + let mut srv = init_service( + App::new() + .service(web::resource("/").to(|_: web::Data| HttpResponse::Ok())) + .configure(cfg2), + ); + let req = TestRequest::default().to_request(); + let resp = block_on(srv.call(req)).unwrap(); + assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR); + } +} diff --git a/src/web.rs b/src/web.rs index 65b3cfc70..94c98c22a 100644 --- a/src/web.rs +++ b/src/web.rs @@ -13,6 +13,7 @@ use crate::responder::Responder; use crate::route::Route; use crate::scope::Scope; +pub use crate::config::RouterConfig; pub use crate::data::{Data, RouteData}; pub use crate::request::HttpRequest; pub use crate::types::*;