diff --git a/CHANGES.md b/CHANGES.md index 0dc7be27..b5078d53 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,9 +1,11 @@ # Changes -## [1.0.0] - 2019-05-xx +## [1.0.0] - 2019-06-xx ### Add +* Add `Scope::configure()` method. + * Add `ServiceRequest::set_payload()` method. * Add `test::TestRequest::set_json()` convenience method to automatically diff --git a/Cargo.toml b/Cargo.toml index e8fdb136..e689fab0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -102,7 +102,7 @@ rustls = { version = "0.15", optional = true } [dev-dependencies] actix-http = { version = "0.2.2", features=["ssl", "brotli", "flate2-zlib"] } actix-http-test = { version = "0.2.0", features=["ssl"] } -actix-files = { version = "0.1.0" } +actix-files = { version = "0.1.1" } rand = "0.6" env_logger = "0.6" serde_derive = "1.0" diff --git a/src/rmap.rs b/src/rmap.rs index 35fe8ee3..6a543b75 100644 --- a/src/rmap.rs +++ b/src/rmap.rs @@ -122,7 +122,9 @@ impl ResourceMap { I: AsRef, { if let Some(pattern) = self.named.get(name) { - self.fill_root(path, elements)?; + if pattern.pattern().starts_with("/") { + self.fill_root(path, elements)?; + } if pattern.resource_path(path, elements) { Ok(Some(())) } else { diff --git a/src/scope.rs b/src/scope.rs index e9b60c6e..ad97fcb6 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -11,6 +11,7 @@ use actix_service::{ use futures::future::{ok, Either, Future, FutureResult}; use futures::{Async, IntoFuture, Poll}; +use crate::config::ServiceConfig; use crate::data::Data; use crate::dev::{AppService, HttpServiceFactory}; use crate::error::Error; @@ -21,7 +22,6 @@ use crate::route::Route; use crate::service::{ ServiceFactory, ServiceFactoryWrapper, ServiceRequest, ServiceResponse, }; -use crate::config::ServiceConfig; type Guards = Vec>; type HttpService = BoxedService; @@ -67,6 +67,7 @@ pub struct Scope { services: Vec>, guards: Vec>, default: Rc>>>, + external: Vec, factory_ref: Rc>>, } @@ -81,59 +82,10 @@ impl Scope { guards: Vec::new(), services: Vec::new(), default: Rc::new(RefCell::new(None)), + external: Vec::new(), 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::ServiceConfig) { - /// 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 ServiceConfig), - { - let mut cfg = ServiceConfig::new(); - f(&mut cfg); - self.services.extend(cfg.services); - - if !cfg.data.is_empty() { - let mut data = self.data.unwrap_or(Extensions::new()); - - for value in cfg.data.iter() { - value.create(&mut data); - } - - self.data = Some(data); - } - self - } } impl Scope @@ -204,6 +156,56 @@ where self } + /// 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::ServiceConfig) { + /// 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 ServiceConfig), + { + let mut cfg = ServiceConfig::new(); + f(&mut cfg); + self.services.extend(cfg.services); + self.external.extend(cfg.external); + + if !cfg.data.is_empty() { + let mut data = self.data.unwrap_or_else(|| Extensions::new()); + + for value in cfg.data.iter() { + value.create(&mut data); + } + + self.data = Some(data); + } + self + } + /// Register http service. /// /// This is similar to `App's` service registration. @@ -332,6 +334,7 @@ where guards: self.guards, services: self.services, default: self.default, + external: self.external, factory_ref: self.factory_ref, } } @@ -410,6 +413,11 @@ where let mut rmap = ResourceMap::new(ResourceDef::root_prefix(&self.rdef)); + // external resources + for mut rdef in std::mem::replace(&mut self.external, Vec::new()) { + rmap.add(&mut rdef, None); + } + // custom app data storage if let Some(ref mut ext) = self.data { config.set_service_data(ext); @@ -645,7 +653,7 @@ mod tests { use crate::dev::{Body, ResponseBody}; use crate::http::{header, HeaderValue, Method, StatusCode}; use crate::service::{ServiceRequest, ServiceResponse}; - use crate::test::{block_on, call_service, init_service, TestRequest}; + use crate::test::{block_on, call_service, init_service, read_body, TestRequest}; use crate::{guard, web, App, Error, HttpRequest, HttpResponse}; #[test] @@ -1076,14 +1084,10 @@ mod tests { #[test] fn test_scope_config() { - let mut srv = init_service( - App::new().service( - web::scope("/app") - .configure(|s|{ - s.route("/path1", web::get().to(||HttpResponse::Ok())); - }) - ), - ); + let mut srv = + init_service(App::new().service(web::scope("/app").configure(|s| { + s.route("/path1", web::get().to(|| HttpResponse::Ok())); + }))); let req = TestRequest::with_uri("/app/path1").to_request(); let resp = block_on(srv.call(req)).unwrap(); @@ -1092,21 +1096,43 @@ mod tests { #[test] fn test_scope_config_2() { - let mut srv = init_service( - App::new().service( - web::scope("/app") - .configure(|s|{ - s.service( - web::scope("/v1") - .configure(|s|{ - s.route("/", web::get().to(||HttpResponse::Ok())); - })); - }) - ), - ); + let mut srv = + init_service(App::new().service(web::scope("/app").configure(|s| { + s.service(web::scope("/v1").configure(|s| { + s.route("/", web::get().to(|| HttpResponse::Ok())); + })); + }))); let req = TestRequest::with_uri("/app/v1/").to_request(); let resp = block_on(srv.call(req)).unwrap(); assert_eq!(resp.status(), StatusCode::OK); } + + #[test] + fn test_url_for_external() { + let mut srv = + init_service(App::new().service(web::scope("/app").configure(|s| { + s.service(web::scope("/v1").configure(|s| { + s.external_resource( + "youtube", + "https://youtube.com/watch/{video_id}", + ); + s.route( + "/", + web::get().to(|req: HttpRequest| { + HttpResponse::Ok().body(format!( + "{}", + req.url_for("youtube", &["xxxxxx"]).unwrap().as_str() + )) + }), + ); + })); + }))); + + let req = TestRequest::with_uri("/app/v1/").to_request(); + let resp = block_on(srv.call(req)).unwrap(); + assert_eq!(resp.status(), StatusCode::OK); + let body = read_body(resp); + assert_eq!(body, &b"https://youtube.com/watch/xxxxxx"[..]); + } }