From 7167f15bc36a27400876c98f7422755d49f16277 Mon Sep 17 00:00:00 2001 From: Denys Vitali Date: Fri, 31 May 2019 17:52:42 +0200 Subject: [PATCH] WIP: Scope configuarion --- actix-http/src/extensions.rs | 22 +++++++++++ src/config.rs | 55 ++++++++++++++++++++++++++- src/scope.rs | 73 ++++++++++++++++++++++++++++++++++++ 3 files changed, 149 insertions(+), 1 deletion(-) diff --git a/actix-http/src/extensions.rs b/actix-http/src/extensions.rs index 148e4c18e..c227c9247 100644 --- a/actix-http/src/extensions.rs +++ b/actix-http/src/extensions.rs @@ -2,6 +2,8 @@ use std::any::{Any, TypeId}; use std::fmt; use hashbrown::HashMap; +use hashbrown::hash_map::{Keys, Values}; +use hashbrown::hash_map::IntoIter; #[derive(Default)] /// A type map of request extensions. @@ -18,6 +20,14 @@ impl Extensions { } } + /// Create `Extensions` with the provided content + #[inline] + pub fn from(e: Extensions) -> Extensions{ + Extensions { + map: HashMap::from(e.map) + } + } + /// Insert a type into this `Extensions`. /// /// If a extension of this type already existed, it will @@ -62,6 +72,18 @@ impl Extensions { pub fn clear(&mut self) { self.map.clear(); } + + pub fn into_iter(&self) -> IntoIter> { + self.map.into_iter() + } + + pub fn keys(& self) -> Keys> { + self.map.keys() + } + + pub fn values(& self) -> Values> { + self.map.values() + } } impl fmt::Debug for Extensions { diff --git a/src/config.rs b/src/config.rs index bc33da9df..843133034 100644 --- a/src/config.rs +++ b/src/config.rs @@ -15,6 +15,7 @@ use crate::service::{ HttpServiceFactory, ServiceFactory, ServiceFactoryWrapper, ServiceRequest, ServiceResponse, }; +use core::borrow::{Borrow, BorrowMut}; type Guards = Vec>; type HttpNewService = @@ -188,7 +189,7 @@ impl ServiceConfig { } } - /// Set application data. Applicatin data could be accessed + /// Set application data. Application data could be accessed /// by using `Data` extractor where `T` is data type. /// /// This is same as `App::data()` method. @@ -239,6 +240,58 @@ impl ServiceConfig { } } +pub struct ScopeConfig { + pub(crate) services: Vec>, + pub(crate) guards: Vec>, + pub(crate) data: Option, +} + +impl ScopeConfig { + pub(crate) fn new() -> Self { + Self { + services: Vec::new(), + guards: Vec::new(), + data: Some(Extensions::new()), + } + } + + /// Set application data. Application 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 { + if self.data.is_none() { + self.data = Some(Extensions::new()); + } + + self.data.as_mut().unwrap().insert(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 + } +} + #[cfg(test)] mod tests { use actix_service::Service; diff --git a/src/scope.rs b/src/scope.rs index 84f34dae6..254ce551c 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -21,6 +21,7 @@ use crate::route::Route; use crate::service::{ ServiceFactory, ServiceFactoryWrapper, ServiceRequest, ServiceResponse, }; +use crate::config::ScopeConfig; type Guards = Vec>; type HttpService = BoxedService; @@ -83,6 +84,62 @@ impl Scope { factory_ref: fref, } } + + + /// Run external configuration as part of the scope 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::ScopeConfig) { + /// 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()) + /// .service( + /// web::scope("/api") + /// .configure(config) + /// ) + /// .route("/index.html", web::get().to(|| HttpResponse::Ok())); + /// } + /// ``` + pub fn configure(mut self, f: F) -> Self + where + F: FnOnce(&mut ScopeConfig), + { + let mut cfg = ScopeConfig::new(); + f(&mut cfg); + self.services.extend(cfg.services); + self.guards.extend(cfg.guards); + //self.data.extend(cfg.data); + + if cfg.data.is_some() { + if (&self).data.is_some() { + + let mut new_data = Extensions::from(cfg.data.unwrap()); + + let old_data = Extensions::from(self.data.take().unwrap()); + for value in old_data.into_iter() { + new_data.insert(value); + } + + self.data = Some(new_data); + } + } + self + } } impl Scope @@ -1022,4 +1079,20 @@ mod tests { let resp = call_service(&mut srv, req); assert_eq!(resp.status(), StatusCode::OK); } + + #[test] + fn test_scope_config() { + let mut srv = init_service( + App::new().service( + web::scope("/app") + .configure(||{ + + }) + ), + ); + + let req = TestRequest::with_uri("/app/path1").to_request(); + let resp = block_on(srv.call(req)).unwrap(); + assert_eq!(resp.status(), StatusCode::OK); + } }