use std::marker::PhantomData; use std::mem; use std::rc::Rc; use futures::{Async, Future, Poll}; use error::Error; use handler::{ AsyncResult, AsyncResultItem, FromRequest, Handler, Responder, RouteHandler, WrapHandler, }; use http::Method; use httprequest::HttpRequest; use httpresponse::HttpResponse; use middleware::{ Finished as MiddlewareFinished, Middleware, Response as MiddlewareResponse, Started as MiddlewareStarted, }; use pred::Predicate; use resource::{DefaultResource, Resource}; use router::{ResourceDef, Router}; use server::Request; use with::WithFactory; /// Resources scope /// /// Scope is a set of resources with common root path. /// Scopes collect multiple paths under a common path prefix. /// Scope path can contain variable path segments as resources. /// Scope prefix is always complete path segment, i.e `/app` would /// be converted to a `/app/` and it would not match `/app` path. /// /// You can get variable path segments from `HttpRequest::match_info()`. /// `Path` extractor also is able to extract scope level variable segments. /// /// ```rust /// # extern crate actix_web; /// use actix_web::{http, App, HttpRequest, HttpResponse}; /// /// fn main() { /// let app = App::new().scope("/{project_id}/", |scope| { /// scope /// .resource("/path1", |r| r.f(|_| HttpResponse::Ok())) /// .resource("/path2", |r| r.f(|_| HttpResponse::Ok())) /// .resource("/path3", |r| r.f(|_| HttpResponse::MethodNotAllowed())) /// }); /// } /// ``` /// /// In the above example three routes get registered: /// * /{project_id}/path1 - reponds to all http method /// * /{project_id}/path2 - `GET` requests /// * /{project_id}/path3 - `HEAD` requests /// pub struct Scope { rdef: ResourceDef, router: Rc>, filters: Vec>>, middlewares: Rc>>>, } #[cfg_attr(feature = "cargo-clippy", allow(new_without_default_derive))] impl Scope { /// Create a new scope pub fn new(path: &str) -> Scope { let rdef = ResourceDef::prefix(path); Scope { rdef: rdef.clone(), router: Rc::new(Router::new(rdef)), filters: Vec::new(), middlewares: Rc::new(Vec::new()), } } #[inline] pub(crate) fn rdef(&self) -> &ResourceDef { &self.rdef } pub(crate) fn router(&self) -> &Router { self.router.as_ref() } #[inline] pub(crate) fn take_filters(&mut self) -> Vec>> { mem::replace(&mut self.filters, Vec::new()) } /// Add match predicate to scope. /// /// ```rust /// # extern crate actix_web; /// use actix_web::{http, pred, App, HttpRequest, HttpResponse, Path}; /// /// fn index(data: Path<(String, String)>) -> &'static str { /// "Welcome!" /// } /// /// fn main() { /// let app = App::new().scope("/app", |scope| { /// scope /// .filter(pred::Header("content-type", "text/plain")) /// .route("/test1", http::Method::GET, index) /// .route("/test2", http::Method::POST, |_: HttpRequest| { /// HttpResponse::MethodNotAllowed() /// }) /// }); /// } /// ``` pub fn filter + 'static>(mut self, p: T) -> Self { self.filters.push(Box::new(p)); self } /// Create nested scope with new state. /// /// ```rust /// # extern crate actix_web; /// use actix_web::{App, HttpRequest}; /// /// struct AppState; /// /// fn index(req: &HttpRequest) -> &'static str { /// "Welcome!" /// } /// /// fn main() { /// let app = App::new().scope("/app", |scope| { /// scope.with_state("/state2", AppState, |scope| { /// scope.resource("/test1", |r| r.f(index)) /// }) /// }); /// } /// ``` pub fn with_state(mut self, path: &str, state: T, f: F) -> Scope where F: FnOnce(Scope) -> Scope, { let rdef = ResourceDef::prefix(path); let scope = Scope { rdef: rdef.clone(), filters: Vec::new(), router: Rc::new(Router::new(rdef)), middlewares: Rc::new(Vec::new()), }; let mut scope = f(scope); let state = Rc::new(state); let filters: Vec>> = vec![Box::new(FiltersWrapper { state: Rc::clone(&state), filters: scope.take_filters(), })]; let handler = Box::new(Wrapper { scope, state }); Rc::get_mut(&mut self.router).unwrap().register_handler( path, handler, Some(filters), ); self } /// Create nested scope. /// /// ```rust /// # extern crate actix_web; /// use actix_web::{App, HttpRequest}; /// /// struct AppState; /// /// fn index(req: &HttpRequest) -> &'static str { /// "Welcome!" /// } /// /// fn main() { /// let app = App::with_state(AppState).scope("/app", |scope| { /// scope.nested("/v1", |scope| scope.resource("/test1", |r| r.f(index))) /// }); /// } /// ``` pub fn nested(mut self, path: &str, f: F) -> Scope where F: FnOnce(Scope) -> Scope, { let rdef = ResourceDef::prefix(&insert_slash(path)); let scope = Scope { rdef: rdef.clone(), filters: Vec::new(), router: Rc::new(Router::new(rdef)), middlewares: Rc::new(Vec::new()), }; Rc::get_mut(&mut self.router) .unwrap() .register_scope(f(scope)); self } /// Configure route for a specific path. /// /// This is a simplified version of the `Scope::resource()` method. /// Handler functions need to accept one request extractor /// argument. /// /// This method could be called multiple times, in that case /// multiple routes would be registered for same resource path. /// /// ```rust /// # extern crate actix_web; /// use actix_web::{http, App, HttpRequest, HttpResponse, Path}; /// /// fn index(data: Path<(String, String)>) -> &'static str { /// "Welcome!" /// } /// /// fn main() { /// let app = App::new().scope("/app", |scope| { /// scope.route("/test1", http::Method::GET, index).route( /// "/test2", /// http::Method::POST, /// |_: HttpRequest| HttpResponse::MethodNotAllowed(), /// ) /// }); /// } /// ``` pub fn route(mut self, path: &str, method: Method, f: F) -> Scope where F: WithFactory, R: Responder + 'static, T: FromRequest + 'static, { Rc::get_mut(&mut self.router).unwrap().register_route( &insert_slash(path), method, f, ); self } /// Configure resource for a specific path. /// /// This method is similar to an `App::resource()` method. /// Resources may have variable path segments. Resource path uses scope /// path as a path prefix. /// /// ```rust /// # extern crate actix_web; /// use actix_web::*; /// /// fn main() { /// let app = App::new().scope("/api", |scope| { /// scope.resource("/users/{userid}/{friend}", |r| { /// r.get().f(|_| HttpResponse::Ok()); /// r.head().f(|_| HttpResponse::MethodNotAllowed()); /// r.route() /// .filter(pred::Any(pred::Get()).or(pred::Put())) /// .filter(pred::Header("Content-Type", "text/plain")) /// .f(|_| HttpResponse::Ok()) /// }) /// }); /// } /// ``` pub fn resource(mut self, path: &str, f: F) -> Scope where F: FnOnce(&mut Resource) -> R + 'static, { // add resource let mut resource = Resource::new(ResourceDef::new(&insert_slash(path))); f(&mut resource); Rc::get_mut(&mut self.router) .unwrap() .register_resource(resource); self } /// Default resource to be used if no matching route could be found. pub fn default_resource(mut self, f: F) -> Scope where F: FnOnce(&mut Resource) -> R + 'static, { // create and configure default resource let mut resource = Resource::new(ResourceDef::new("")); f(&mut resource); Rc::get_mut(&mut self.router) .expect("Multiple copies of scope router") .register_default_resource(resource.into()); self } /// Configure handler for specific path prefix. /// /// A path prefix consists of valid path segments, i.e for the /// prefix `/app` any request with the paths `/app`, `/app/` or /// `/app/test` would match, but the path `/application` would /// not. /// /// ```rust /// # extern crate actix_web; /// use actix_web::{http, App, HttpRequest, HttpResponse}; /// /// fn main() { /// let app = App::new().scope("/scope-prefix", |scope| { /// scope.handler("/app", |req: &HttpRequest| match *req.method() { /// http::Method::GET => HttpResponse::Ok(), /// http::Method::POST => HttpResponse::MethodNotAllowed(), /// _ => HttpResponse::NotFound(), /// }) /// }); /// } /// ``` pub fn handler>(mut self, path: &str, handler: H) -> Scope { let path = insert_slash(path.trim().trim_right_matches('/')); Rc::get_mut(&mut self.router) .expect("Multiple copies of scope router") .register_handler(&path, Box::new(WrapHandler::new(handler)), None); self } /// Register a scope middleware /// /// This is similar to `App's` middlewares, but /// middlewares get invoked on scope level. /// /// *Note* `Middleware::finish()` fires right after response get /// prepared. It does not wait until body get sent to the peer. pub fn middleware>(mut self, mw: M) -> Scope { Rc::get_mut(&mut self.middlewares) .expect("Can not use after configuration") .push(Box::new(mw)); self } } fn insert_slash(path: &str) -> String { let mut path = path.to_owned(); if !path.is_empty() && !path.starts_with('/') { path.insert(0, '/'); }; path } impl RouteHandler for Scope { fn handle(&self, req: &HttpRequest) -> AsyncResult { let tail = req.match_info().tail as usize; // recognize resources let info = self.router.recognize(req, req.state(), tail); let req2 = req.with_route_info(info); if self.middlewares.is_empty() { self.router.handle(&req2) } else { AsyncResult::async(Box::new(Compose::new( req2, Rc::clone(&self.router), Rc::clone(&self.middlewares), ))) } } fn has_default_resource(&self) -> bool { self.router.has_default_resource() } fn default_resource(&mut self, default: DefaultResource) { Rc::get_mut(&mut self.router) .expect("Can not use after configuration") .register_default_resource(default); } fn finish(&mut self) { Rc::get_mut(&mut self.router) .expect("Can not use after configuration") .finish(); } } struct Wrapper { state: Rc, scope: Scope, } impl RouteHandler for Wrapper { fn handle(&self, req: &HttpRequest) -> AsyncResult { let req = req.with_state(Rc::clone(&self.state)); self.scope.handle(&req) } } struct FiltersWrapper { state: Rc, filters: Vec>>, } impl Predicate for FiltersWrapper { fn check(&self, req: &Request, _: &S2) -> bool { for filter in &self.filters { if !filter.check(&req, &self.state) { return false; } } true } } /// Compose resource level middlewares with route handler. struct Compose { info: ComposeInfo, state: ComposeState, } struct ComposeInfo { count: usize, req: HttpRequest, router: Rc>, mws: Rc>>>, } enum ComposeState { Starting(StartMiddlewares), Handler(WaitingResponse), RunMiddlewares(RunMiddlewares), Finishing(FinishingMiddlewares), Completed(Response), } impl ComposeState { fn poll(&mut self, info: &mut ComposeInfo) -> Option> { match *self { ComposeState::Starting(ref mut state) => state.poll(info), ComposeState::Handler(ref mut state) => state.poll(info), ComposeState::RunMiddlewares(ref mut state) => state.poll(info), ComposeState::Finishing(ref mut state) => state.poll(info), ComposeState::Completed(_) => None, } } } impl Compose { fn new( req: HttpRequest, router: Rc>, mws: Rc>>>, ) -> Self { let mut info = ComposeInfo { mws, req, router, count: 0, }; let state = StartMiddlewares::init(&mut info); Compose { state, info } } } impl Future for Compose { type Item = HttpResponse; type Error = Error; fn poll(&mut self) -> Poll { loop { if let ComposeState::Completed(ref mut resp) = self.state { let resp = resp.resp.take().unwrap(); return Ok(Async::Ready(resp)); } if let Some(state) = self.state.poll(&mut self.info) { self.state = state; } else { return Ok(Async::NotReady); } } } } /// Middlewares start executor struct StartMiddlewares { fut: Option, _s: PhantomData, } type Fut = Box, Error = Error>>; impl StartMiddlewares { fn init(info: &mut ComposeInfo) -> ComposeState { let len = info.mws.len(); loop { if info.count == len { let reply = info.router.handle(&info.req); return WaitingResponse::init(info, reply); } else { let result = info.mws[info.count].start(&info.req); match result { Ok(MiddlewareStarted::Done) => info.count += 1, Ok(MiddlewareStarted::Response(resp)) => { return RunMiddlewares::init(info, resp); } Ok(MiddlewareStarted::Future(fut)) => { return ComposeState::Starting(StartMiddlewares { fut: Some(fut), _s: PhantomData, }); } Err(err) => { return RunMiddlewares::init(info, err.into()); } } } } } fn poll(&mut self, info: &mut ComposeInfo) -> Option> { let len = info.mws.len(); 'outer: loop { match self.fut.as_mut().unwrap().poll() { Ok(Async::NotReady) => { return None; } Ok(Async::Ready(resp)) => { info.count += 1; if let Some(resp) = resp { return Some(RunMiddlewares::init(info, resp)); } loop { if info.count == len { let reply = info.router.handle(&info.req); return Some(WaitingResponse::init(info, reply)); } else { let result = info.mws[info.count].start(&info.req); match result { Ok(MiddlewareStarted::Done) => info.count += 1, Ok(MiddlewareStarted::Response(resp)) => { return Some(RunMiddlewares::init(info, resp)); } Ok(MiddlewareStarted::Future(fut)) => { self.fut = Some(fut); continue 'outer; } Err(err) => { return Some(RunMiddlewares::init(info, err.into())); } } } } } Err(err) => { return Some(RunMiddlewares::init(info, err.into())); } } } } } // waiting for response struct WaitingResponse { fut: Box>, _s: PhantomData, } impl WaitingResponse { #[inline] fn init( info: &mut ComposeInfo, reply: AsyncResult, ) -> ComposeState { match reply.into() { AsyncResultItem::Ok(resp) => RunMiddlewares::init(info, resp), AsyncResultItem::Err(err) => RunMiddlewares::init(info, err.into()), AsyncResultItem::Future(fut) => ComposeState::Handler(WaitingResponse { fut, _s: PhantomData, }), } } fn poll(&mut self, info: &mut ComposeInfo) -> Option> { match self.fut.poll() { Ok(Async::NotReady) => None, Ok(Async::Ready(resp)) => Some(RunMiddlewares::init(info, resp)), Err(err) => Some(RunMiddlewares::init(info, err.into())), } } } /// Middlewares response executor struct RunMiddlewares { curr: usize, fut: Option>>, _s: PhantomData, } impl RunMiddlewares { fn init(info: &mut ComposeInfo, mut resp: HttpResponse) -> ComposeState { let mut curr = 0; let len = info.mws.len(); loop { let state = info.mws[curr].response(&info.req, resp); resp = match state { Err(err) => { info.count = curr + 1; return FinishingMiddlewares::init(info, err.into()); } Ok(MiddlewareResponse::Done(r)) => { curr += 1; if curr == len { return FinishingMiddlewares::init(info, r); } else { r } } Ok(MiddlewareResponse::Future(fut)) => { return ComposeState::RunMiddlewares(RunMiddlewares { curr, fut: Some(fut), _s: PhantomData, }); } }; } } fn poll(&mut self, info: &mut ComposeInfo) -> Option> { let len = info.mws.len(); loop { // poll latest fut let mut resp = match self.fut.as_mut().unwrap().poll() { Ok(Async::NotReady) => return None, Ok(Async::Ready(resp)) => { self.curr += 1; resp } Err(err) => return Some(FinishingMiddlewares::init(info, err.into())), }; loop { if self.curr == len { return Some(FinishingMiddlewares::init(info, resp)); } else { let state = info.mws[self.curr].response(&info.req, resp); match state { Err(err) => { return Some(FinishingMiddlewares::init(info, err.into())) } Ok(MiddlewareResponse::Done(r)) => { self.curr += 1; resp = r } Ok(MiddlewareResponse::Future(fut)) => { self.fut = Some(fut); break; } } } } } } } /// Middlewares start executor struct FinishingMiddlewares { resp: Option, fut: Option>>, _s: PhantomData, } impl FinishingMiddlewares { fn init(info: &mut ComposeInfo, resp: HttpResponse) -> ComposeState { if info.count == 0 { Response::init(resp) } else { let mut state = FinishingMiddlewares { resp: Some(resp), fut: None, _s: PhantomData, }; if let Some(st) = state.poll(info) { st } else { ComposeState::Finishing(state) } } } fn poll(&mut self, info: &mut ComposeInfo) -> Option> { loop { // poll latest fut let not_ready = if let Some(ref mut fut) = self.fut { match fut.poll() { Ok(Async::NotReady) => true, Ok(Async::Ready(())) => false, Err(err) => { error!("Middleware finish error: {}", err); false } } } else { false }; if not_ready { return None; } self.fut = None; if info.count == 0 { return Some(Response::init(self.resp.take().unwrap())); } info.count -= 1; let state = info.mws[info.count as usize] .finish(&info.req, self.resp.as_ref().unwrap()); match state { MiddlewareFinished::Done => { if info.count == 0 { return Some(Response::init(self.resp.take().unwrap())); } } MiddlewareFinished::Future(fut) => { self.fut = Some(fut); } } } } } struct Response { resp: Option, _s: PhantomData, } impl Response { fn init(resp: HttpResponse) -> ComposeState { ComposeState::Completed(Response { resp: Some(resp), _s: PhantomData, }) } } #[cfg(test)] mod tests { use bytes::Bytes; use application::App; use body::Body; use http::{Method, StatusCode}; use httprequest::HttpRequest; use httpresponse::HttpResponse; use pred; use test::TestRequest; #[test] fn test_scope() { let app = App::new() .scope("/app", |scope| { scope.resource("/path1", |r| r.f(|_| HttpResponse::Ok())) }).finish(); let req = TestRequest::with_uri("/app/path1").request(); let resp = app.run(req); assert_eq!(resp.as_msg().status(), StatusCode::OK); } #[test] fn test_scope_root() { let app = App::new() .scope("/app", |scope| { scope .resource("", |r| r.f(|_| HttpResponse::Ok())) .resource("/", |r| r.f(|_| HttpResponse::Created())) }).finish(); let req = TestRequest::with_uri("/app").request(); let resp = app.run(req); assert_eq!(resp.as_msg().status(), StatusCode::OK); let req = TestRequest::with_uri("/app/").request(); let resp = app.run(req); assert_eq!(resp.as_msg().status(), StatusCode::CREATED); } #[test] fn test_scope_root2() { let app = App::new() .scope("/app/", |scope| { scope.resource("", |r| r.f(|_| HttpResponse::Ok())) }).finish(); let req = TestRequest::with_uri("/app").request(); let resp = app.run(req); assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); let req = TestRequest::with_uri("/app/").request(); let resp = app.run(req); assert_eq!(resp.as_msg().status(), StatusCode::OK); } #[test] fn test_scope_root3() { let app = App::new() .scope("/app/", |scope| { scope.resource("/", |r| r.f(|_| HttpResponse::Ok())) }).finish(); let req = TestRequest::with_uri("/app").request(); let resp = app.run(req); assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); let req = TestRequest::with_uri("/app/").request(); let resp = app.run(req); assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); } #[test] fn test_scope_route() { let app = App::new() .scope("app", |scope| { scope .route("/path1", Method::GET, |_: HttpRequest<_>| { HttpResponse::Ok() }).route("/path1", Method::DELETE, |_: HttpRequest<_>| { HttpResponse::Ok() }) }).finish(); let req = TestRequest::with_uri("/app/path1").request(); let resp = app.run(req); assert_eq!(resp.as_msg().status(), StatusCode::OK); let req = TestRequest::with_uri("/app/path1") .method(Method::DELETE) .request(); let resp = app.run(req); assert_eq!(resp.as_msg().status(), StatusCode::OK); let req = TestRequest::with_uri("/app/path1") .method(Method::POST) .request(); let resp = app.run(req); assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); } #[test] fn test_scope_route_without_leading_slash() { let app = App::new() .scope("app", |scope| { scope .route("path1", Method::GET, |_: HttpRequest<_>| HttpResponse::Ok()) .route("path1", Method::DELETE, |_: HttpRequest<_>| { HttpResponse::Ok() }) }).finish(); let req = TestRequest::with_uri("/app/path1").request(); let resp = app.run(req); assert_eq!(resp.as_msg().status(), StatusCode::OK); let req = TestRequest::with_uri("/app/path1") .method(Method::DELETE) .request(); let resp = app.run(req); assert_eq!(resp.as_msg().status(), StatusCode::OK); let req = TestRequest::with_uri("/app/path1") .method(Method::POST) .request(); let resp = app.run(req); assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); } #[test] fn test_scope_filter() { let app = App::new() .scope("/app", |scope| { scope .filter(pred::Get()) .resource("/path1", |r| r.f(|_| HttpResponse::Ok())) }).finish(); let req = TestRequest::with_uri("/app/path1") .method(Method::POST) .request(); let resp = app.run(req); assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); let req = TestRequest::with_uri("/app/path1") .method(Method::GET) .request(); let resp = app.run(req); assert_eq!(resp.as_msg().status(), StatusCode::OK); } #[test] fn test_scope_variable_segment() { let app = App::new() .scope("/ab-{project}", |scope| { scope.resource("/path1", |r| { r.f(|r| { HttpResponse::Ok() .body(format!("project: {}", &r.match_info()["project"])) }) }) }).finish(); let req = TestRequest::with_uri("/ab-project1/path1").request(); let resp = app.run(req); assert_eq!(resp.as_msg().status(), StatusCode::OK); match resp.as_msg().body() { &Body::Binary(ref b) => { let bytes: Bytes = b.clone().into(); assert_eq!(bytes, Bytes::from_static(b"project: project1")); } _ => panic!(), } let req = TestRequest::with_uri("/aa-project1/path1").request(); let resp = app.run(req); assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); } #[test] fn test_scope_with_state() { struct State; let app = App::new() .scope("/app", |scope| { scope.with_state("/t1", State, |scope| { scope.resource("/path1", |r| r.f(|_| HttpResponse::Created())) }) }).finish(); let req = TestRequest::with_uri("/app/t1/path1").request(); let resp = app.run(req); assert_eq!(resp.as_msg().status(), StatusCode::CREATED); } #[test] fn test_scope_with_state_root() { struct State; let app = App::new() .scope("/app", |scope| { scope.with_state("/t1", State, |scope| { scope .resource("", |r| r.f(|_| HttpResponse::Ok())) .resource("/", |r| r.f(|_| HttpResponse::Created())) }) }).finish(); let req = TestRequest::with_uri("/app/t1").request(); let resp = app.run(req); assert_eq!(resp.as_msg().status(), StatusCode::OK); let req = TestRequest::with_uri("/app/t1/").request(); let resp = app.run(req); assert_eq!(resp.as_msg().status(), StatusCode::CREATED); } #[test] fn test_scope_with_state_root2() { struct State; let app = App::new() .scope("/app", |scope| { scope.with_state("/t1/", State, |scope| { scope.resource("", |r| r.f(|_| HttpResponse::Ok())) }) }).finish(); let req = TestRequest::with_uri("/app/t1").request(); let resp = app.run(req); assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); let req = TestRequest::with_uri("/app/t1/").request(); let resp = app.run(req); assert_eq!(resp.as_msg().status(), StatusCode::OK); } #[test] fn test_scope_with_state_root3() { struct State; let app = App::new() .scope("/app", |scope| { scope.with_state("/t1/", State, |scope| { scope.resource("/", |r| r.f(|_| HttpResponse::Ok())) }) }).finish(); let req = TestRequest::with_uri("/app/t1").request(); let resp = app.run(req); assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); let req = TestRequest::with_uri("/app/t1/").request(); let resp = app.run(req); assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); } #[test] fn test_scope_with_state_filter() { struct State; let app = App::new() .scope("/app", |scope| { scope.with_state("/t1", State, |scope| { scope .filter(pred::Get()) .resource("/path1", |r| r.f(|_| HttpResponse::Ok())) }) }).finish(); let req = TestRequest::with_uri("/app/t1/path1") .method(Method::POST) .request(); let resp = app.run(req); assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); let req = TestRequest::with_uri("/app/t1/path1") .method(Method::GET) .request(); let resp = app.run(req); assert_eq!(resp.as_msg().status(), StatusCode::OK); } #[test] fn test_nested_scope() { let app = App::new() .scope("/app", |scope| { scope.nested("/t1", |scope| { scope.resource("/path1", |r| r.f(|_| HttpResponse::Created())) }) }).finish(); let req = TestRequest::with_uri("/app/t1/path1").request(); let resp = app.run(req); assert_eq!(resp.as_msg().status(), StatusCode::CREATED); } #[test] fn test_nested_scope_no_slash() { let app = App::new() .scope("/app", |scope| { scope.nested("t1", |scope| { scope.resource("/path1", |r| r.f(|_| HttpResponse::Created())) }) }).finish(); let req = TestRequest::with_uri("/app/t1/path1").request(); let resp = app.run(req); assert_eq!(resp.as_msg().status(), StatusCode::CREATED); } #[test] fn test_nested_scope_root() { let app = App::new() .scope("/app", |scope| { scope.nested("/t1", |scope| { scope .resource("", |r| r.f(|_| HttpResponse::Ok())) .resource("/", |r| r.f(|_| HttpResponse::Created())) }) }).finish(); let req = TestRequest::with_uri("/app/t1").request(); let resp = app.run(req); assert_eq!(resp.as_msg().status(), StatusCode::OK); let req = TestRequest::with_uri("/app/t1/").request(); let resp = app.run(req); assert_eq!(resp.as_msg().status(), StatusCode::CREATED); } #[test] fn test_nested_scope_filter() { let app = App::new() .scope("/app", |scope| { scope.nested("/t1", |scope| { scope .filter(pred::Get()) .resource("/path1", |r| r.f(|_| HttpResponse::Ok())) }) }).finish(); let req = TestRequest::with_uri("/app/t1/path1") .method(Method::POST) .request(); let resp = app.run(req); assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); let req = TestRequest::with_uri("/app/t1/path1") .method(Method::GET) .request(); let resp = app.run(req); assert_eq!(resp.as_msg().status(), StatusCode::OK); } #[test] fn test_nested_scope_with_variable_segment() { let app = App::new() .scope("/app", |scope| { scope.nested("/{project_id}", |scope| { scope.resource("/path1", |r| { r.f(|r| { HttpResponse::Created().body(format!( "project: {}", &r.match_info()["project_id"] )) }) }) }) }).finish(); let req = TestRequest::with_uri("/app/project_1/path1").request(); let resp = app.run(req); assert_eq!(resp.as_msg().status(), StatusCode::CREATED); match resp.as_msg().body() { &Body::Binary(ref b) => { let bytes: Bytes = b.clone().into(); assert_eq!(bytes, Bytes::from_static(b"project: project_1")); } _ => panic!(), } } #[test] fn test_nested2_scope_with_variable_segment() { let app = App::new() .scope("/app", |scope| { scope.nested("/{project}", |scope| { scope.nested("/{id}", |scope| { scope.resource("/path1", |r| { r.f(|r| { HttpResponse::Created().body(format!( "project: {} - {}", &r.match_info()["project"], &r.match_info()["id"], )) }) }) }) }) }).finish(); let req = TestRequest::with_uri("/app/test/1/path1").request(); let resp = app.run(req); assert_eq!(resp.as_msg().status(), StatusCode::CREATED); match resp.as_msg().body() { &Body::Binary(ref b) => { let bytes: Bytes = b.clone().into(); assert_eq!(bytes, Bytes::from_static(b"project: test - 1")); } _ => panic!(), } let req = TestRequest::with_uri("/app/test/1/path2").request(); let resp = app.run(req); assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); } #[test] fn test_default_resource() { let app = App::new() .scope("/app", |scope| { scope .resource("/path1", |r| r.f(|_| HttpResponse::Ok())) .default_resource(|r| r.f(|_| HttpResponse::BadRequest())) }).finish(); let req = TestRequest::with_uri("/app/path2").request(); let resp = app.run(req); assert_eq!(resp.as_msg().status(), StatusCode::BAD_REQUEST); let req = TestRequest::with_uri("/path2").request(); let resp = app.run(req); assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); } #[test] fn test_default_resource_propagation() { let app = App::new() .scope("/app1", |scope| { scope.default_resource(|r| r.f(|_| HttpResponse::BadRequest())) }).scope("/app2", |scope| scope) .default_resource(|r| r.f(|_| HttpResponse::MethodNotAllowed())) .finish(); let req = TestRequest::with_uri("/non-exist").request(); let resp = app.run(req); assert_eq!(resp.as_msg().status(), StatusCode::METHOD_NOT_ALLOWED); let req = TestRequest::with_uri("/app1/non-exist").request(); let resp = app.run(req); assert_eq!(resp.as_msg().status(), StatusCode::BAD_REQUEST); let req = TestRequest::with_uri("/app2/non-exist").request(); let resp = app.run(req); assert_eq!(resp.as_msg().status(), StatusCode::METHOD_NOT_ALLOWED); } #[test] fn test_handler() { let app = App::new() .scope("/scope", |scope| { scope.handler("/test", |_: &_| HttpResponse::Ok()) }).finish(); let req = TestRequest::with_uri("/scope/test").request(); let resp = app.run(req); assert_eq!(resp.as_msg().status(), StatusCode::OK); let req = TestRequest::with_uri("/scope/test/").request(); let resp = app.run(req); assert_eq!(resp.as_msg().status(), StatusCode::OK); let req = TestRequest::with_uri("/scope/test/app").request(); let resp = app.run(req); assert_eq!(resp.as_msg().status(), StatusCode::OK); let req = TestRequest::with_uri("/scope/testapp").request(); let resp = app.run(req); assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); let req = TestRequest::with_uri("/scope/blah").request(); let resp = app.run(req); assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); } }