diff --git a/CHANGES.md b/CHANGES.md index 0dc7be27c..b5078d531 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 e8fdb1362..e689fab0e 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/config.rs b/src/config.rs index bc33da9df..8de43f36c 100644 --- a/src/config.rs +++ b/src/config.rs @@ -188,7 +188,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. diff --git a/src/rmap.rs b/src/rmap.rs index 35fe8ee32..6a543b75c 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 84f34dae6..ad97fcb62 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; @@ -66,6 +67,7 @@ pub struct Scope { services: Vec>, guards: Vec>, default: Rc>>>, + external: Vec, factory_ref: Rc>>, } @@ -80,6 +82,7 @@ impl Scope { guards: Vec::new(), services: Vec::new(), default: Rc::new(RefCell::new(None)), + external: Vec::new(), factory_ref: fref, } } @@ -153,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. @@ -281,6 +334,7 @@ where guards: self.guards, services: self.services, default: self.default, + external: self.external, factory_ref: self.factory_ref, } } @@ -359,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); @@ -594,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] @@ -1022,4 +1081,58 @@ 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(|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(); + assert_eq!(resp.status(), StatusCode::OK); + } + + #[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 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"[..]); + } }