use std::marker::PhantomData; use std::mem; use std::rc::Rc; use futures::{Async, Future, Poll}; use error::Error; use handler::{AsyncResult, AsyncResultItem, FromRequest, Responder, RouteHandler}; use http::{Method, StatusCode}; use httprequest::HttpRequest; use httpresponse::HttpResponse; use middleware::{ Finished as MiddlewareFinished, Middleware, Response as MiddlewareResponse, Started as MiddlewareStarted, }; use pred::Predicate; use resource::{ResourceHandler, RouteId}; use router::Resource; use server::Request; type ScopeResource = Rc>; type Route = Box>; type ScopeResources = Rc)>>; type NestedInfo = (Resource, Route, Vec>>); /// 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 /// #[derive(Default)] pub struct Scope { filters: Vec>>, nested: Vec>, middlewares: Rc>>>, default: Option>, resources: ScopeResources, } #[cfg_attr(feature = "cargo-clippy", allow(new_without_default_derive))] impl Scope { /// Create a new scope // TODO: Why is this not exactly the default impl? pub fn new() -> Scope { Scope { filters: Vec::new(), nested: Vec::new(), resources: Rc::new(Vec::new()), middlewares: Rc::new(Vec::new()), default: None, } } #[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 scope = Scope { filters: Vec::new(), nested: Vec::new(), resources: Rc::new(Vec::new()), middlewares: Rc::new(Vec::new()), default: None, }; 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 }); self.nested .push((Resource::prefix("", &path), handler, 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 scope = Scope { filters: Vec::new(), nested: Vec::new(), resources: Rc::new(Vec::new()), middlewares: Rc::new(Vec::new()), default: None, }; let mut scope = f(scope); let filters = scope.take_filters(); self.nested .push((Resource::prefix("", &path), Box::new(scope), filters)); 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: Fn(T) -> R + 'static, R: Responder + 'static, T: FromRequest + 'static, { // check if we have resource handler let mut found = false; for &(ref pattern, _) in self.resources.iter() { if pattern.pattern() == path { found = true; break; } } if found { let resources = Rc::get_mut(&mut self.resources) .expect("Multiple scope references are not allowed"); for &mut (ref pattern, ref mut resource) in resources.iter_mut() { if pattern.pattern() == path { let res = Rc::get_mut(resource) .expect("Multiple scope references are not allowed"); res.method(method).with(f); break; } } } else { let mut handler = ResourceHandler::default(); handler.method(method).with(f); let pattern = Resource::with_prefix( handler.get_name(), path, if path.is_empty() { "" } else { "/" }, false, ); Rc::get_mut(&mut self.resources) .expect("Can not use after configuration") .push((pattern, Rc::new(handler))); } 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 ResourceHandler) -> R + 'static, { // add resource handler let mut handler = ResourceHandler::default(); f(&mut handler); let pattern = Resource::with_prefix( handler.get_name(), path, if path.is_empty() { "" } else { "/" }, false, ); Rc::get_mut(&mut self.resources) .expect("Can not use after configuration") .push((pattern, Rc::new(handler))); 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 ResourceHandler) -> R + 'static, { if self.default.is_none() { self.default = Some(Rc::new(ResourceHandler::default_not_found())); } { let default = Rc::get_mut(self.default.as_mut().unwrap()) .expect("Multiple copies of default handler"); f(default); } 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 } } impl RouteHandler for Scope { fn handle(&self, req: &HttpRequest) -> AsyncResult { let tail = req.match_info().tail as usize; // recognize resources for &(ref pattern, ref resource) in self.resources.iter() { if let Some(params) = pattern.match_with_params(req, tail, false) { let req2 = req.with_route_info(req.route().merge(params)); if let Some(id) = resource.get_route_id(&req2) { if self.middlewares.is_empty() { return resource.handle(id, &req2); } else { return AsyncResult::async(Box::new(Compose::new( id, req2, Rc::clone(&self.middlewares), Rc::clone(&resource), ))); } } } } // nested scopes let len = req.route().prefix_len() as usize; 'outer: for &(ref prefix, ref handler, ref filters) in &self.nested { if let Some(params) = prefix.match_prefix_with_params(req, tail) { let req2 = req.with_route_info(req.route().merge(params)); let state = req.state(); for filter in filters { if !filter.check(&req2, state) { continue 'outer; } } return handler.handle(&req2); } } // default handler if let Some(ref resource) = self.default { if let Some(id) = resource.get_route_id(req) { if self.middlewares.is_empty() { return resource.handle(id, req); } else { return AsyncResult::async(Box::new(Compose::new( id, req.clone(), Rc::clone(&self.middlewares), Rc::clone(resource), ))); } } } AsyncResult::ok(HttpResponse::new(StatusCode::NOT_FOUND)) } fn has_default_resource(&self) -> bool { self.default.is_some() } fn default_resource(&mut self, default: ScopeResource) { self.default = Some(default); } } 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, id: RouteId, req: HttpRequest, mws: Rc>>>, resource: 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( id: RouteId, req: HttpRequest, mws: Rc>>>, resource: Rc>, ) -> Self { let mut info = ComposeInfo { id, mws, req, resource, 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.resource.handle(info.id, &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.resource.handle(info.id, &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_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_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); } }