1use std::{cell::RefCell, marker::PhantomData, rc::Rc};
2
3use crate::error::{Failure, IntoFailure};
4use crate::http::Response;
5use crate::router::{IntoPattern, ResourceDef, Router};
6use crate::service::{Identity, ServiceChainFactory, boxed};
7use crate::{IntoServiceFactory, Middleware, Service, ServiceFactory, factory};
8
9use super::app_service::WebServiceRouter;
10use super::dev::{WebServiceConfig, WebServiceFactory};
11use super::error::{WebError, WebResponseError};
12use super::guard::Guard;
13use super::rmap::ResourceMap;
14use super::service::{AppServiceFactory, ServiceFactoryWrapper};
15use super::stack::{Filter, WebStack};
16use super::{HttpService, Resource, Route, ServiceConfig, State, WebRequest, WebResponse};
17
18type Guards = Vec<Box<dyn Guard>>;
19
20#[derive(derive_more::Debug)]
60#[debug("Scope({rdef:?})")]
61pub struct Scope<St: State, In, Out = In, M = Identity, F = Filter<St, In>> {
62 middleware: M,
63 filter: ServiceChainFactory<F, St, WebRequest<In>>,
64 rdef: Vec<String>,
65 guards: Vec<Box<dyn Guard>>,
66 external: Vec<ResourceDef>,
67 case_insensitive: bool,
68 ph: PhantomData<Out>,
69}
70
71#[derive(derive_more::Debug)]
78#[debug("ScopeServices({rdef:?})")]
79pub struct ScopeServices<St: State, In, Out, M, F> {
80 middleware: M,
81 rdef: Vec<String>,
82 guards: Vec<Box<dyn Guard>>,
83 filter: ServiceChainFactory<F, St, WebRequest<In>>,
84 services: Vec<Box<dyn AppServiceFactory<St, Out>>>,
85 default: Option<HttpService<St, Out>>,
86 external: Vec<ResourceDef>,
87 case_insensitive: bool,
88}
89
90impl<St: State, In> Scope<St, In, In> {
91 #[allow(clippy::needless_pass_by_value)]
92 pub fn new<T: IntoPattern>(path: T) -> Self {
94 Scope {
95 middleware: Identity,
96 filter: factory(Filter::new()),
97 rdef: path.patterns(),
98 guards: Vec::new(),
99 external: Vec::new(),
100 case_insensitive: false,
101 ph: PhantomData,
102 }
103 }
104}
105
106impl<St, In, Out, M, F> Scope<St, In, Out, M, F>
107where
108 St: State,
109 In: 'static,
110 Out: 'static,
111 F: ServiceFactory<
112 St,
113 WebRequest<In>,
114 Res = WebRequest<Out>,
115 Error = WebError<St, St::Error>,
116 InitError = Failure,
117 >,
118{
119 #[must_use]
138 pub fn guard<G: Guard + 'static>(mut self, guard: G) -> Self {
139 self.guards.push(Box::new(guard));
140 self
141 }
142
143 #[must_use]
147 pub fn case_insensitive_routing(mut self) -> Self {
148 self.case_insensitive = true;
149 self
150 }
151
152 #[must_use]
181 pub fn configure(
182 self,
183 f: impl FnOnce(&mut ServiceConfig<St, Out>),
184 ) -> ScopeServices<St, In, Out, M, F> {
185 let mut cfg = ServiceConfig::new(self.external);
186 f(&mut cfg);
187
188 ScopeServices {
189 rdef: self.rdef,
190 guards: self.guards,
191 filter: self.filter,
192 middleware: self.middleware,
193 default: None,
194 services: cfg.services,
195 external: cfg.external,
196 case_insensitive: self.case_insensitive,
197 }
198 }
199
200 #[must_use]
222 pub fn service(
223 self,
224 factory: impl WebServiceFactory<St, Out>,
225 ) -> ScopeServices<St, In, Out, M, F> {
226 ScopeServices {
227 rdef: self.rdef,
228 guards: self.guards,
229 filter: self.filter,
230 middleware: self.middleware,
231 default: None,
232 external: self.external,
233 case_insensitive: self.case_insensitive,
234 services: vec![Box::new(ServiceFactoryWrapper::new(factory))],
235 }
236 }
237
238 #[must_use]
265 pub fn route(self, path: &str, mut route: Route<St, Out>) -> ScopeServices<St, In, Out, M, F> {
266 self.service(
267 Resource::new(path)
268 .add_guards(route.take_guards())
269 .route(route),
270 )
271 }
272
273 #[must_use]
296 pub fn default_service<Sf>(
297 self,
298 f: impl IntoServiceFactory<Sf, St, WebRequest<Out>>,
299 ) -> ScopeServices<St, In, Out, M, F>
300 where
301 Sf: ServiceFactory<St, WebRequest<Out>, Res = WebResponse> + 'static,
302 Sf::Error: WebResponseError<St, St::Error>,
303 Sf::InitError: IntoFailure,
304 {
305 let default = boxed::factory(
307 f.into_factory()
308 .map_err(WebError::from_err)
309 .map_init_err(IntoFailure::fail),
310 );
311
312 ScopeServices {
313 rdef: self.rdef,
314 guards: self.guards,
315 filter: self.filter,
316 middleware: self.middleware,
317 default: Some(default),
318 external: self.external,
319 case_insensitive: self.case_insensitive,
320 services: Vec::new(),
321 }
322 }
323
324 #[must_use]
347 pub fn filter<U, R>(
348 self,
349 filter: impl IntoServiceFactory<U, St, WebRequest<Out>>,
350 ) -> Scope<
351 St,
352 In,
353 R,
354 M,
355 impl ServiceFactory<
356 St,
357 WebRequest<In>,
358 Res = WebRequest<R>,
359 Error = WebError<St, St::Error>,
360 InitError = Failure,
361 >,
362 >
363 where
364 U: ServiceFactory<St, WebRequest<Out>, Res = WebRequest<R>>,
365 U::Error: WebResponseError<St, St::Error>,
366 U::InitError: IntoFailure,
367 {
368 Scope {
369 filter: self.filter.and_then(
370 filter
371 .into_factory()
372 .map_err(WebError::from_err)
373 .map_init_err(IntoFailure::fail),
374 ),
375 middleware: self.middleware,
376 rdef: self.rdef,
377 guards: self.guards,
378 external: self.external,
379 case_insensitive: self.case_insensitive,
380 ph: PhantomData,
381 }
382 }
383
384 #[must_use]
404 pub fn middleware<U>(self, mw: U) -> Scope<St, In, Out, WebStack<St, U, M>, F> {
405 Scope {
406 middleware: WebStack::new(mw, self.middleware),
407 filter: self.filter,
408 rdef: self.rdef,
409 guards: self.guards,
410 external: self.external,
411 case_insensitive: self.case_insensitive,
412 ph: PhantomData,
413 }
414 }
415}
416
417impl<St, In, Out, M, F> ScopeServices<St, In, Out, M, F>
418where
419 St: State,
420 In: 'static,
421 Out: 'static,
422 F: ServiceFactory<
423 St,
424 WebRequest<In>,
425 Res = WebRequest<Out>,
426 Error = WebError<St, St::Error>,
427 InitError = Failure,
428 >,
429{
430 #[must_use]
437 pub fn service(mut self, factory: impl WebServiceFactory<St, Out>) -> Self {
438 self.services
439 .push(Box::new(ServiceFactoryWrapper::new(factory)));
440 self
441 }
442
443 #[must_use]
465 pub fn route(self, path: &str, mut route: Route<St, Out>) -> Self {
466 self.service(
467 Resource::new(path)
468 .add_guards(route.take_guards())
469 .route(route),
470 )
471 }
472
473 #[must_use]
496 pub fn default_service<Sf>(
497 mut self,
498 f: impl IntoServiceFactory<Sf, St, WebRequest<Out>>,
499 ) -> Self
500 where
501 Sf: ServiceFactory<St, WebRequest<Out>, Res = WebResponse> + 'static,
502 Sf::Error: WebResponseError<St, St::Error>,
503 Sf::InitError: IntoFailure,
504 {
505 self.default = Some(boxed::factory(
507 f.into_factory()
508 .map_err(WebError::from_err)
509 .map_init_err(IntoFailure::fail),
510 ));
511
512 self
513 }
514}
515
516impl<St, Outer, In, Out, M, F> WebServiceFactory<St, Outer> for ScopeServices<St, In, Out, M, F>
517where
518 St: State,
519 Outer: 'static,
520 In: 'static,
521 Out: 'static,
522 F: ServiceFactory<
523 St,
524 WebRequest<In>,
525 Res = WebRequest<Out>,
526 Error = WebError<St, St::Error>,
527 InitError = Failure,
528 > + 'static,
529 M: Middleware<WebServiceRouter<St, In, Out, F::Service>, St> + 'static,
530 M::Service: Service<St, WebRequest<Outer>, Res = WebResponse, Error = WebError<St, St::Error>>,
531{
532 fn register(mut self, config: &mut WebServiceConfig<St, Outer>) {
533 let default = self.default.unwrap_or_else(|| {
535 boxed::factory(
536 factory(async move |req: WebRequest<Out>| {
537 Ok(req.into_response(Response::NotFound().build()))
538 })
539 .map_init_err(|_| unreachable!()),
540 )
541 });
542
543 let mut cfg = WebServiceConfig::new();
545
546 for mut svc in self.services {
548 svc.register(&mut cfg);
549 }
550
551 let slash = self.rdef.iter().any(|s| s.ends_with('/'));
553 let mut rmap = ResourceMap::new(ResourceDef::root_prefix(self.rdef.clone()));
554
555 for mut rdef in std::mem::take(&mut self.external) {
556 rmap.add(&mut rdef, None);
557 }
558
559 let services: Vec<_> = cfg
561 .into_services()
562 .into_iter()
563 .map(|(rdef, srv, guards, nested)| {
564 let mut rdef = if slash && rdef.pattern() == "" {
567 ResourceDef::new("/")
568 } else {
569 rdef
570 };
571 rmap.add(&mut rdef, nested);
572 (rdef, srv, RefCell::new(guards))
573 })
574 .collect();
575
576 let mut router = Router::builder();
578 if self.case_insensitive {
579 router.case_insensitive();
580 }
581 for (path, factory, guards) in services {
582 router
583 .resource(path.clone(), factory)
584 .set_check_value(guards.borrow_mut().take());
585 }
586
587 config.register_service(
589 ResourceDef::root_prefix(self.rdef),
590 if self.guards.is_empty() {
591 None
592 } else {
593 Some(self.guards)
594 },
595 Some(Rc::new(rmap)),
596 ScopeServiceFactory {
597 default,
598 middleware: self.middleware,
599 filter: self.filter,
600 router: Rc::new(router.build()),
601 },
602 );
603 }
604}
605
606struct ScopeServiceFactory<St: State, In, Out, M, F> {
608 middleware: M,
609 filter: ServiceChainFactory<F, St, WebRequest<In>>,
610 router: Rc<Router<HttpService<St, Out>, Guards>>,
611 default: HttpService<St, Out>,
612}
613
614impl<St, Outer, In, Out, M, F> ServiceFactory<St, WebRequest<Outer>>
615 for ScopeServiceFactory<St, In, Out, M, F>
616where
617 St: State,
618 Outer: 'static,
619 In: 'static,
620 Out: 'static,
621 F: ServiceFactory<
622 St,
623 WebRequest<In>,
624 Res = WebRequest<Out>,
625 Error = WebError<St, St::Error>,
626 InitError = Failure,
627 > + 'static,
628 M: Middleware<WebServiceRouter<St, In, Out, F::Service>, St> + 'static,
629 M::Service: Service<St, WebRequest<Outer>, Res = WebResponse, Error = WebError<St, St::Error>>,
630{
631 type Res = WebResponse;
632 type Error = WebError<St, St::Error>;
633
634 type Service = M::Service;
635 type InitError = Failure;
636
637 async fn create(&self, st: &St) -> Result<Self::Service, Self::InitError> {
638 let filter = self.filter.create(st).await?;
639
640 Ok(self.middleware.create(
642 st,
643 WebServiceRouter::new(filter, self.router.clone(), self.default.clone()),
644 ))
645 }
646}
647
648#[cfg(test)]
649mod tests {
650 use std::convert::Infallible;
651
652 use crate::http::body::{Body, ResponseBody};
653 use crate::http::header::{CONTENT_TYPE, HeaderValue};
654 use crate::http::{Method, StatusCode};
655 use crate::util::Bytes;
656 use crate::web::middleware::DefaultHeaders;
657 use crate::web::test::{TestRequest, call_service, init_service, read_body};
658 use crate::web::{self, App, HttpRequest, HttpResponse, WebRequest, guard};
659
660 #[crate::rt_test]
661 async fn test_scope() {
662 let srv = init_service(
663 App::new()
664 .service(
665 web::scope("/app")
666 .service(web::resource("/path1").to(async || HttpResponse::Ok())),
667 )
668 .service(
669 web::scope("/app2")
670 .case_insensitive_routing()
671 .service(web::resource("/path1").to(async || HttpResponse::Ok())),
672 ),
673 )
674 .await;
675
676 let req = TestRequest::with_uri("/app/path1").to_request();
677 let resp = srv.call(req).await.unwrap();
678 assert_eq!(resp.status(), StatusCode::OK);
679
680 let req = TestRequest::with_uri("/app/path10").to_request();
681 let resp = srv.call(req).await.unwrap();
682 assert_eq!(resp.status(), StatusCode::NOT_FOUND);
683
684 let req = TestRequest::with_uri("/app2/path1").to_request();
685 let resp = srv.call(req).await.unwrap();
686 assert_eq!(resp.status(), StatusCode::OK);
687
688 let req = TestRequest::with_uri("/app2/Path1").to_request();
689 let resp = srv.call(req).await.unwrap();
690 assert_eq!(resp.status(), StatusCode::OK);
691 }
692
693 #[crate::rt_test]
694 async fn test_scope_root() {
695 let srv = init_service(
696 App::new().service(
697 web::scope("/app")
698 .service(web::resource("").to(async || HttpResponse::Ok()))
699 .service(web::resource("/").to(async || HttpResponse::Created())),
700 ),
701 )
702 .await;
703
704 let req = TestRequest::with_uri("/app").to_request();
705 let resp = srv.call(req).await.unwrap();
706 assert_eq!(resp.status(), StatusCode::OK);
707
708 let req = TestRequest::with_uri("/app/").to_request();
709 let resp = srv.call(req).await.unwrap();
710 assert_eq!(resp.status(), StatusCode::CREATED);
711 }
712
713 #[crate::rt_test]
714 async fn test_scope_root_multi() {
715 let srv = init_service(
716 App::new().service(
717 web::scope(["/app", "/app2"])
718 .service(web::resource("").to(async || HttpResponse::Ok()))
719 .service(web::resource("/").to(async || HttpResponse::Created())),
720 ),
721 )
722 .await;
723
724 for url in &["/app", "/app2"] {
725 let req = TestRequest::with_uri(url).to_request();
726 let resp = srv.call(req).await.unwrap();
727 assert_eq!(resp.status(), StatusCode::OK);
728 }
729
730 for url in &["/app/", "/app2/"] {
731 let req = TestRequest::with_uri(url).to_request();
732 let resp = srv.call(req).await.unwrap();
733 assert_eq!(resp.status(), StatusCode::CREATED);
734 }
735 }
736
737 #[crate::rt_test]
738 async fn test_scope_root2() {
739 let srv = init_service(App::new().service(
740 web::scope("/app/").service(web::resource("").to(async || HttpResponse::Ok())),
741 ))
742 .await;
743
744 let req = TestRequest::with_uri("/app").to_request();
745 let resp = srv.call(req).await.unwrap();
746 assert_eq!(resp.status(), StatusCode::NOT_FOUND);
747
748 let req = TestRequest::with_uri("/app/").to_request();
749 let resp = srv.call(req).await.unwrap();
750 assert_eq!(resp.status(), StatusCode::OK);
751 }
752
753 #[crate::rt_test]
754 async fn test_scope_root2_multi() {
755 let srv = init_service(
756 App::new().service(
757 web::scope(["/app/", "/app2/"])
758 .service(web::resource("").to(async || HttpResponse::Ok())),
759 ),
760 )
761 .await;
762
763 for url in &["/app", "/app2"] {
764 let req = TestRequest::with_uri(url).to_request();
765 let resp = srv.call(req).await.unwrap();
766 assert_eq!(resp.status(), StatusCode::NOT_FOUND);
767 }
768
769 for url in &["/app/", "/app2/"] {
770 let req = TestRequest::with_uri(url).to_request();
771 let resp = srv.call(req).await.unwrap();
772 assert_eq!(resp.status(), StatusCode::OK);
773 }
774 }
775
776 #[crate::rt_test]
777 async fn test_scope_root3() {
778 let srv = init_service(App::new().service(
779 web::scope("/app/").service(web::resource("/").to(async || HttpResponse::Ok())),
780 ))
781 .await;
782
783 let req = TestRequest::with_uri("/app").to_request();
784 let resp = srv.call(req).await.unwrap();
785 assert_eq!(resp.status(), StatusCode::NOT_FOUND);
786
787 let req = TestRequest::with_uri("/app/").to_request();
788 let resp = srv.call(req).await.unwrap();
789 assert_eq!(resp.status(), StatusCode::OK);
790 }
791
792 #[crate::rt_test]
793 async fn test_scope_route() {
794 let srv = init_service(
795 App::new().service(
796 web::scope("app")
797 .route("/path1", web::get().to(async || HttpResponse::Ok()))
798 .route("/path1", web::delete().to(async || HttpResponse::Ok())),
799 ),
800 )
801 .await;
802
803 for (m, status) in &[
804 (Method::GET, StatusCode::OK),
805 (Method::DELETE, StatusCode::OK),
806 (Method::POST, StatusCode::NOT_FOUND),
807 ] {
808 let req = TestRequest::with_uri("/app/path1")
809 .method(m.clone())
810 .to_request();
811 let resp = srv.call(req).await.unwrap();
812 assert_eq!(resp.status(), status.clone());
813 }
814 }
815
816 #[crate::rt_test]
817 async fn test_scope_route_multi() {
818 let srv = init_service(
819 App::new().service(
820 web::scope(["app", "app2"])
821 .route("/path1", web::get().to(async || HttpResponse::Ok()))
822 .route("/path1", web::delete().to(async || HttpResponse::Ok())),
823 ),
824 )
825 .await;
826
827 for (m, status) in &[
828 (Method::GET, StatusCode::OK),
829 (Method::DELETE, StatusCode::OK),
830 (Method::POST, StatusCode::NOT_FOUND),
831 ] {
832 let req = TestRequest::with_uri("/app/path1")
833 .method(m.clone())
834 .to_request();
835 let resp = srv.call(req).await.unwrap();
836 assert_eq!(resp.status(), status.clone());
837
838 let req = TestRequest::with_uri("/app2/path1")
839 .method(m.clone())
840 .to_request();
841 let resp = srv.call(req).await.unwrap();
842 assert_eq!(resp.status(), status.clone());
843 }
844 }
845
846 #[crate::rt_test]
847 async fn test_scope_route_without_leading_slash() {
848 let srv = init_service(
849 App::new().service(
850 web::scope("app").service(
851 web::resource("path1")
852 .route(web::get().to(async || HttpResponse::Ok()))
853 .route(web::delete().to(async || HttpResponse::Ok())),
854 ),
855 ),
856 )
857 .await;
858
859 let req = TestRequest::with_uri("/app/path1").to_request();
860 let resp = srv.call(req).await.unwrap();
861 assert_eq!(resp.status(), StatusCode::OK);
862
863 let req = TestRequest::with_uri("/app/path1")
864 .method(Method::DELETE)
865 .to_request();
866 let resp = srv.call(req).await.unwrap();
867 assert_eq!(resp.status(), StatusCode::OK);
868
869 let req = TestRequest::with_uri("/app/path1")
870 .method(Method::POST)
871 .to_request();
872 let resp = srv.call(req).await.unwrap();
873 assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED);
874 }
875
876 #[crate::rt_test]
877 async fn test_scope_guard() {
878 let srv = init_service(
879 App::new()
880 .service(
881 web::scope("/app")
882 .guard(guard::Get())
883 .service(web::resource("/path1").to(async || HttpResponse::Ok())),
884 )
885 .service(
886 web::scope("/app")
887 .guard(guard::Post())
888 .service(web::resource("/path1").to(async || HttpResponse::NotModified())),
889 )
890 .service(web::resource("/app/path1").to(async || HttpResponse::NoContent())),
891 )
892 .await;
893
894 let req = TestRequest::with_uri("/app/path1")
895 .method(Method::POST)
896 .to_request();
897 let resp = srv.call(req).await.unwrap();
898 assert_eq!(resp.status(), StatusCode::NOT_MODIFIED);
899
900 let req = TestRequest::with_uri("/app/path1")
901 .method(Method::GET)
902 .to_request();
903 let resp = srv.call(req).await.unwrap();
904 assert_eq!(resp.status(), StatusCode::OK);
905
906 let req = TestRequest::with_uri("/app/path1")
907 .method(Method::DELETE)
908 .to_request();
909 let resp = srv.call(req).await.unwrap();
910 assert_eq!(resp.status(), StatusCode::NO_CONTENT);
911 }
912
913 #[crate::rt_test]
914 async fn test_scope_variable_segment() {
915 let srv = init_service(App::new().service(web::scope("/ab-{project}").service(
916 web::resource("/path1").to(async move |r: HttpRequest| {
917 HttpResponse::Ok().body(format!("project: {}", &r.match_info()["project"]))
918 }),
919 )))
920 .await;
921
922 let req = TestRequest::with_uri("/ab-project1/path1").to_request();
923 let resp = srv.call(req).await.unwrap();
924 assert_eq!(resp.status(), StatusCode::OK);
925
926 if let ResponseBody::Body(Body::Bytes(b)) = resp.body() {
927 let bytes: Bytes = b.clone();
928 assert_eq!(bytes, Bytes::from_static(b"project: project1"));
929 }
930
931 let req = TestRequest::with_uri("/aa-project1/path1").to_request();
932 let resp = srv.call(req).await.unwrap();
933 assert_eq!(resp.status(), StatusCode::NOT_FOUND);
934 }
935
936 #[crate::rt_test]
937 async fn test_scope_variable_segment2() {
938 let srv = init_service(App::new().service(web::scope("/ab-{project}").service(
939 web::resource(["", "/"]).to(async move |r: HttpRequest| {
940 HttpResponse::Ok().body(format!("project: {}", &r.match_info()["project"]))
941 }),
942 )))
943 .await;
944
945 let req = TestRequest::with_uri("/ab-project1").to_request();
946 let resp = srv.call(req).await.unwrap();
947 assert_eq!(resp.status(), StatusCode::OK);
948
949 if let ResponseBody::Body(Body::Bytes(b)) = resp.body() {
950 let bytes: Bytes = b.clone();
951 assert_eq!(bytes, Bytes::from_static(b"project: project1"));
952 }
953
954 let req = TestRequest::with_uri("/ab-project1/").to_request();
955 let resp = srv.call(req).await.unwrap();
956 assert_eq!(resp.status(), StatusCode::OK);
957
958 if let ResponseBody::Body(Body::Bytes(b)) = resp.body() {
959 let bytes: Bytes = b.clone();
960 assert_eq!(bytes, Bytes::from_static(b"project: project1"));
961 }
962
963 let req = TestRequest::with_uri("/aa-project1").to_request();
964 let resp = srv.call(req).await.unwrap();
965 assert_eq!(resp.status(), StatusCode::NOT_FOUND);
966 }
967
968 #[crate::rt_test]
969 async fn test_nested_scope() {
970 let srv = init_service(App::new().service(web::scope("/app").service(
971 web::scope("/t1").service(web::resource("/path1").to(async || HttpResponse::Created())),
972 )))
973 .await;
974
975 let req = TestRequest::with_uri("/app/t1/path1").to_request();
976 let resp = srv.call(req).await.unwrap();
977 assert_eq!(resp.status(), StatusCode::CREATED);
978 }
979
980 #[crate::rt_test]
981 async fn test_nested_scope_no_slash() {
982 let srv = init_service(App::new().service(web::scope("/app").service(
983 web::scope("t1").service(web::resource("/path1").to(async || HttpResponse::Created())),
984 )))
985 .await;
986
987 let req = TestRequest::with_uri("/app/t1/path1").to_request();
988 let resp = srv.call(req).await.unwrap();
989 assert_eq!(resp.status(), StatusCode::CREATED);
990 }
991
992 #[crate::rt_test]
993 async fn test_nested_scope_root() {
994 let srv = init_service(
995 App::new().service(
996 web::scope("/app").service(
997 web::scope("/t1")
998 .service(web::resource("").to(async || HttpResponse::Ok()))
999 .service(web::resource("/").to(async || HttpResponse::Created())),
1000 ),
1001 ),
1002 )
1003 .await;
1004
1005 let req = TestRequest::with_uri("/app/t1").to_request();
1006 let resp = srv.call(req).await.unwrap();
1007 assert_eq!(resp.status(), StatusCode::OK);
1008
1009 let req = TestRequest::with_uri("/app/t1/").to_request();
1010 let resp = srv.call(req).await.unwrap();
1011 assert_eq!(resp.status(), StatusCode::CREATED);
1012 }
1013
1014 #[crate::rt_test]
1015 async fn test_nested_scope_filter() {
1016 let srv = init_service(
1017 App::new().service(
1018 web::scope("/app").service(
1019 web::scope("/t1")
1020 .guard(guard::Get())
1021 .service(web::resource("/path1").to(async || HttpResponse::Ok())),
1022 ),
1023 ),
1024 )
1025 .await;
1026
1027 let req = TestRequest::with_uri("/app/t1/path1")
1028 .method(Method::POST)
1029 .to_request();
1030 let resp = srv.call(req).await.unwrap();
1031 assert_eq!(resp.status(), StatusCode::NOT_FOUND);
1032
1033 let req = TestRequest::with_uri("/app/t1/path1")
1034 .method(Method::GET)
1035 .to_request();
1036 let resp = srv.call(req).await.unwrap();
1037 assert_eq!(resp.status(), StatusCode::OK);
1038 }
1039
1040 #[crate::rt_test]
1041 async fn test_nested_scope_with_variable_segment() {
1042 let srv = init_service(App::new().service(web::scope("/app").service(
1043 web::scope("/{project_id}").service(web::resource("/path1").to(
1044 async move |r: HttpRequest| {
1045 HttpResponse::Created()
1046 .body(format!("project: {}", &r.match_info()["project_id"]))
1047 },
1048 )),
1049 )))
1050 .await;
1051
1052 let req = TestRequest::with_uri("/app/project_1/path1").to_request();
1053 let resp = srv.call(req).await.unwrap();
1054 assert_eq!(resp.status(), StatusCode::CREATED);
1055
1056 if let ResponseBody::Body(Body::Bytes(b)) = resp.body() {
1057 let bytes: Bytes = b.clone();
1058 assert_eq!(bytes, Bytes::from_static(b"project: project_1"));
1059 }
1060 }
1061
1062 #[crate::rt_test]
1063 async fn test_nested2_scope_with_variable_segment() {
1064 let srv = init_service(App::new().service(web::scope("/app").service(
1065 web::scope("/{project}").service(web::scope("/{id}").service(
1066 web::resource("/path1").to(async move |r: HttpRequest| {
1067 HttpResponse::Created().body(format!(
1068 "project: {} - {}",
1069 &r.match_info()["project"],
1070 &r.match_info()["id"],
1071 ))
1072 }),
1073 )),
1074 )))
1075 .await;
1076
1077 let req = TestRequest::with_uri("/app/test/1/path1").to_request();
1078 let resp = srv.call(req).await.unwrap();
1079 assert_eq!(resp.status(), StatusCode::CREATED);
1080
1081 if let ResponseBody::Body(Body::Bytes(b)) = resp.body() {
1082 let bytes: Bytes = b.clone();
1083 assert_eq!(bytes, Bytes::from_static(b"project: test - 1"));
1084 }
1085
1086 let req = TestRequest::with_uri("/app/test/1/path2").to_request();
1087 let resp = srv.call(req).await.unwrap();
1088 assert_eq!(resp.status(), StatusCode::NOT_FOUND);
1089 }
1090
1091 #[crate::rt_test]
1092 async fn test_default_resource() {
1093 let srv = init_service(
1094 App::new().service(
1095 web::scope("/app")
1096 .service(web::resource("/path1").to(async || HttpResponse::Ok()))
1097 .default_service(async move |r: WebRequest<()>| {
1098 Ok::<_, Infallible>(r.into_response(HttpResponse::BadRequest()))
1099 }),
1100 ),
1101 )
1102 .await;
1103
1104 let req = TestRequest::with_uri("/app/path2").to_request();
1105 let resp = srv.call(req).await.unwrap();
1106 assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
1107
1108 let req = TestRequest::with_uri("/path2").to_request();
1109 let resp = srv.call(req).await.unwrap();
1110 assert_eq!(resp.status(), StatusCode::NOT_FOUND);
1111 }
1112
1113 #[crate::rt_test]
1114 async fn test_filter() {
1115 let filter = std::rc::Rc::new(std::cell::Cell::new(false));
1116 let filter2 = filter.clone();
1117 let srv = init_service(
1118 App::new().service(
1119 web::scope("app")
1120 .filter(async move |req: WebRequest<()>| {
1121 filter2.set(true);
1122 Ok::<_, Infallible>(req)
1123 })
1124 .route("/test", web::get().to(async || HttpResponse::Ok())),
1125 ),
1126 )
1127 .await;
1128 let req = TestRequest::with_uri("/app/test").to_request();
1129 let resp = call_service(&srv, req).await;
1130 assert_eq!(resp.status(), StatusCode::OK);
1131 assert!(filter.get());
1132 }
1133
1134 #[crate::rt_test]
1135 async fn test_middleware() {
1136 let srv = init_service(
1137 App::new().service(
1138 web::scope("app")
1139 .middleware(
1140 DefaultHeaders::new()
1141 .header(CONTENT_TYPE, HeaderValue::from_static("0001")),
1142 )
1143 .service(
1144 web::resource("/test").route(web::get().to(async || HttpResponse::Ok())),
1145 ),
1146 ),
1147 )
1148 .await;
1149
1150 let req = TestRequest::with_uri("/app/test").to_request();
1151 let resp = call_service(&srv, req).await;
1152 assert_eq!(resp.status(), StatusCode::OK);
1153 assert_eq!(
1154 resp.headers().get(CONTENT_TYPE).unwrap(),
1155 HeaderValue::from_static("0001")
1156 );
1157 }
1158
1159 #[crate::rt_test]
1160 async fn test_scope_config_2() {
1161 let srv = init_service(App::new().service(web::scope("/app").configure(|s| {
1162 s.service(web::scope("/v1").configure(|s| {
1163 s.route("/", web::get().to(async || HttpResponse::Ok()));
1164 }));
1165 })))
1166 .await;
1167
1168 let req = TestRequest::with_uri("/app/v1/").to_request();
1169 let resp = srv.call(req).await.unwrap();
1170 assert_eq!(resp.status(), StatusCode::OK);
1171 }
1172
1173 #[crate::rt_test]
1174 async fn test_url_for_external() {
1175 let srv = init_service(App::new().service(web::scope("/app").configure(|s| {
1176 s.service(web::scope("/v1").configure(|s| {
1177 s.external_resource("youtube", "https://youtube.com/watch/{video_id}");
1178 s.route(
1179 "/",
1180 web::get().to(async move |req: HttpRequest| {
1181 HttpResponse::Ok().body(
1182 req.url_for("youtube", ["xxxxxx"])
1183 .unwrap()
1184 .as_str()
1185 .to_string(),
1186 )
1187 }),
1188 );
1189 }));
1190 })))
1191 .await;
1192
1193 let req = TestRequest::with_uri("/app/v1/").to_request();
1194 let resp = srv.call(req).await.unwrap();
1195 assert_eq!(resp.status(), StatusCode::OK);
1196 let body = read_body(resp).await;
1197 assert_eq!(body, &b"https://youtube.com/watch/xxxxxx"[..]);
1198 }
1199
1200 #[crate::rt_test]
1201 async fn test_url_for_nested() {
1202 let srv = init_service(App::new().service(web::scope("/a").service(
1203 web::scope("/b").service(web::resource("/c/{stuff}").name("c").route(web::get().to(
1204 async move |req: HttpRequest| {
1205 HttpResponse::Ok().body(format!("{}", req.url_for("c", ["12345"]).unwrap()))
1206 },
1207 ))),
1208 )))
1209 .await;
1210
1211 let req = TestRequest::with_uri("/a/b/c/test").to_request();
1212 let resp = call_service(&srv, req).await;
1213 assert_eq!(resp.status(), StatusCode::OK);
1214 let body = read_body(resp).await;
1215 assert_eq!(
1216 body,
1217 Bytes::from_static(b"http://localhost:8080/a/b/c/12345")
1218 );
1219 }
1220
1221 #[crate::rt_test]
1222 async fn test_url_for_parent() {
1223 async fn urls(req: HttpRequest) -> HttpResponse {
1224 let index = req.url_for("index", ["1"]).unwrap();
1225 let youtube = req.url_for("youtube", ["2"]).unwrap();
1226 let sibling = req.url_for("sibling", ["3"]).unwrap();
1227 HttpResponse::Ok().body(format!("{index} {youtube} {sibling}"))
1228 }
1229
1230 let srv = init_service(
1231 App::new()
1232 .external_resource("youtube", "https://youtube.com/watch/{video_id}")
1233 .service(
1234 web::resource("/index/{id}")
1235 .name("index")
1236 .to(async || HttpResponse::Ok()),
1237 )
1238 .service(
1239 web::scope("/sibling").service(
1240 web::resource("/{id}")
1241 .name("sibling")
1242 .to(async || HttpResponse::Ok()),
1243 ),
1244 )
1245 .service(
1246 web::scope("/a")
1247 .service(web::resource("/").to(urls))
1248 .service(web::scope("/b").service(web::resource("/").to(urls))),
1249 ),
1250 )
1251 .await;
1252
1253 for uri in ["/a/", "/a/b/"] {
1254 let req = TestRequest::with_uri(uri).to_request();
1255 let resp = call_service(&srv, req).await;
1256 assert_eq!(resp.status(), StatusCode::OK);
1257 let body = read_body(resp).await;
1258 assert_eq!(
1259 body,
1260 Bytes::from_static(
1261 b"http://localhost:8080/index/1 https://youtube.com/watch/2 http://localhost:8080/sibling/3"
1262 ),
1263 "{uri}"
1264 );
1265 }
1266 }
1267
1268 #[crate::rt_test]
1269 async fn test_scope_default_service_only() {
1270 let srv = init_service(App::new().service(web::scope("/app").default_service(
1271 async |r: WebRequest<()>| Ok::<_, Infallible>(r.into_response(HttpResponse::Created())),
1272 )))
1273 .await;
1274 let resp = call_service(&srv, TestRequest::with_uri("/app/path").to_request()).await;
1275 assert_eq!(resp.status(), StatusCode::CREATED);
1276 let resp = call_service(&srv, TestRequest::with_uri("/other").to_request()).await;
1277 assert_eq!(resp.status(), StatusCode::NOT_FOUND);
1278 }
1279}