actix_web/
scope.rs

1use std::{cell::RefCell, fmt, future::Future, mem, rc::Rc};
2
3use actix_http::{body::MessageBody, Extensions};
4use actix_router::{ResourceDef, Router};
5use actix_service::{
6    apply, apply_fn_factory, boxed, IntoServiceFactory, Service, ServiceFactory, ServiceFactoryExt,
7    Transform,
8};
9use futures_core::future::LocalBoxFuture;
10use futures_util::future::join_all;
11
12use crate::{
13    config::ServiceConfig,
14    data::Data,
15    dev::AppService,
16    guard::Guard,
17    rmap::ResourceMap,
18    service::{
19        AppServiceFactory, BoxedHttpService, BoxedHttpServiceFactory, HttpServiceFactory,
20        ServiceFactoryWrapper, ServiceRequest, ServiceResponse,
21    },
22    Error, Resource, Route,
23};
24
25type Guards = Vec<Box<dyn Guard>>;
26
27/// A collection of [`Route`]s, [`Resource`]s, or other services that share a common path prefix.
28///
29/// The `Scope`'s path can contain [dynamic segments]. The dynamic segments can be extracted from
30/// requests using the [`Path`](crate::web::Path) extractor or
31/// with [`HttpRequest::match_info()`](crate::HttpRequest::match_info).
32///
33/// # Avoid Trailing Slashes
34/// Avoid using trailing slashes in the scope prefix (e.g., `web::scope("/scope/")`). It will almost
35/// certainly not have the expected behavior. See the [documentation on resource definitions][pat]
36/// to understand why this is the case and how to correctly construct scope/prefix definitions.
37///
38/// # Examples
39/// ```
40/// use actix_web::{web, App, HttpResponse};
41///
42/// let app = App::new().service(
43///     web::scope("/{project_id}")
44///         .service(web::resource("/path1").to(|| async { "OK" }))
45///         .service(web::resource("/path2").route(web::get().to(|| HttpResponse::Ok())))
46///         .service(web::resource("/path3").route(web::head().to(HttpResponse::MethodNotAllowed)))
47/// );
48/// ```
49///
50/// In the above example three routes get registered:
51/// - /{project_id}/path1 - responds to all HTTP methods
52/// - /{project_id}/path2 - responds to `GET` requests
53/// - /{project_id}/path3 - responds to `HEAD` requests
54///
55/// [pat]: crate::dev::ResourceDef#prefix-resources
56/// [dynamic segments]: crate::dev::ResourceDef#dynamic-segments
57pub struct Scope<T = ScopeEndpoint> {
58    endpoint: T,
59    rdef: String,
60    app_data: Option<Extensions>,
61    services: Vec<Box<dyn AppServiceFactory>>,
62    guards: Vec<Box<dyn Guard>>,
63    default: Option<Rc<BoxedHttpServiceFactory>>,
64    external: Vec<ResourceDef>,
65    factory_ref: Rc<RefCell<Option<ScopeFactory>>>,
66}
67
68impl Scope {
69    /// Create a new scope
70    pub fn new(path: &str) -> Scope {
71        let factory_ref = Rc::new(RefCell::new(None));
72
73        Scope {
74            endpoint: ScopeEndpoint::new(Rc::clone(&factory_ref)),
75            rdef: path.to_string(),
76            app_data: None,
77            guards: Vec::new(),
78            services: Vec::new(),
79            default: None,
80            external: Vec::new(),
81            factory_ref,
82        }
83    }
84}
85
86impl<T> Scope<T>
87where
88    T: ServiceFactory<ServiceRequest, Config = (), Error = Error, InitError = ()>,
89{
90    /// Add match guard to a scope.
91    ///
92    /// ```
93    /// use actix_web::{web, guard, App, HttpRequest, HttpResponse};
94    ///
95    /// async fn index(data: web::Path<(String, String)>) -> &'static str {
96    ///     "Welcome!"
97    /// }
98    ///
99    /// let app = App::new().service(
100    ///     web::scope("/app")
101    ///         .guard(guard::Header("content-type", "text/plain"))
102    ///         .route("/test1", web::get().to(index))
103    ///         .route("/test2", web::post().to(|r: HttpRequest| {
104    ///             HttpResponse::MethodNotAllowed()
105    ///         }))
106    /// );
107    /// ```
108    pub fn guard<G: Guard + 'static>(mut self, guard: G) -> Self {
109        self.guards.push(Box::new(guard));
110        self
111    }
112
113    /// Add scope data.
114    ///
115    /// Data of different types from parent contexts will still be accessible. Any `Data<T>` types
116    /// set here can be extracted in handlers using the `Data<T>` extractor.
117    ///
118    /// # Examples
119    /// ```
120    /// use std::cell::Cell;
121    /// use actix_web::{web, App, HttpRequest, HttpResponse, Responder};
122    ///
123    /// struct MyData {
124    ///     count: std::cell::Cell<usize>,
125    /// }
126    ///
127    /// async fn handler(req: HttpRequest, counter: web::Data<MyData>) -> impl Responder {
128    ///     // note this cannot use the Data<T> extractor because it was not added with it
129    ///     let incr = *req.app_data::<usize>().unwrap();
130    ///     assert_eq!(incr, 3);
131    ///
132    ///     // update counter using other value from app data
133    ///     counter.count.set(counter.count.get() + incr);
134    ///
135    ///     HttpResponse::Ok().body(counter.count.get().to_string())
136    /// }
137    ///
138    /// let app = App::new().service(
139    ///     web::scope("/app")
140    ///         .app_data(3usize)
141    ///         .app_data(web::Data::new(MyData { count: Default::default() }))
142    ///         .route("/", web::get().to(handler))
143    /// );
144    /// ```
145    #[doc(alias = "manage")]
146    pub fn app_data<U: 'static>(mut self, data: U) -> Self {
147        self.app_data
148            .get_or_insert_with(Extensions::new)
149            .insert(data);
150
151        self
152    }
153
154    /// Add scope data after wrapping in `Data<T>`.
155    ///
156    /// Deprecated in favor of [`app_data`](Self::app_data).
157    #[deprecated(since = "4.0.0", note = "Use `.app_data(Data::new(val))` instead.")]
158    pub fn data<U: 'static>(self, data: U) -> Self {
159        self.app_data(Data::new(data))
160    }
161
162    /// Run external configuration as part of the scope building process.
163    ///
164    /// This function is useful for moving parts of configuration to a different module or library.
165    /// For example, some of the resource's configuration could be moved to different module.
166    ///
167    /// ```
168    /// use actix_web::{web, middleware, App, HttpResponse};
169    ///
170    /// // this function could be located in different module
171    /// fn config(cfg: &mut web::ServiceConfig) {
172    ///     cfg.service(web::resource("/test")
173    ///         .route(web::get().to(|| HttpResponse::Ok()))
174    ///         .route(web::head().to(|| HttpResponse::MethodNotAllowed()))
175    ///     );
176    /// }
177    ///
178    /// let app = App::new()
179    ///     .wrap(middleware::Logger::default())
180    ///     .service(
181    ///         web::scope("/api")
182    ///             .configure(config)
183    ///     )
184    ///     .route("/index.html", web::get().to(|| HttpResponse::Ok()));
185    /// ```
186    pub fn configure<F>(mut self, cfg_fn: F) -> Self
187    where
188        F: FnOnce(&mut ServiceConfig),
189    {
190        let mut cfg = ServiceConfig::new();
191        cfg_fn(&mut cfg);
192
193        self.services.extend(cfg.services);
194        self.external.extend(cfg.external);
195
196        // TODO: add Extensions::is_empty check and conditionally insert data
197        self.app_data
198            .get_or_insert_with(Extensions::new)
199            .extend(cfg.app_data);
200
201        if let Some(default) = cfg.default {
202            self.default = Some(default);
203        }
204
205        self
206    }
207
208    /// Register HTTP service.
209    ///
210    /// This is similar to `App's` service registration.
211    ///
212    /// Actix Web provides several services implementations:
213    ///
214    /// * *Resource* is an entry in resource table which corresponds to requested URL.
215    /// * *Scope* is a set of resources with common root path.
216    ///
217    /// ```
218    /// use actix_web::{web, App, HttpRequest};
219    ///
220    /// struct AppState;
221    ///
222    /// async fn index(req: HttpRequest) -> &'static str {
223    ///     "Welcome!"
224    /// }
225    ///
226    /// let app = App::new().service(
227    ///     web::scope("/app").service(
228    ///         web::scope("/v1")
229    ///             .service(web::resource("/test1").to(index)))
230    /// );
231    /// ```
232    pub fn service<F>(mut self, factory: F) -> Self
233    where
234        F: HttpServiceFactory + 'static,
235    {
236        self.services
237            .push(Box::new(ServiceFactoryWrapper::new(factory)));
238        self
239    }
240
241    /// Configure route for a specific path.
242    ///
243    /// This is a simplified version of the `Scope::service()` method.
244    /// This method can be called multiple times, in that case
245    /// multiple resources with one route would be registered for same resource path.
246    ///
247    /// ```
248    /// use actix_web::{web, App, HttpResponse};
249    ///
250    /// async fn index(data: web::Path<(String, String)>) -> &'static str {
251    ///     "Welcome!"
252    /// }
253    ///
254    /// let app = App::new().service(
255    ///     web::scope("/app")
256    ///         .route("/test1", web::get().to(index))
257    ///         .route("/test2", web::post().to(|| HttpResponse::MethodNotAllowed()))
258    /// );
259    /// ```
260    pub fn route(self, path: &str, mut route: Route) -> Self {
261        self.service(
262            Resource::new(path)
263                .add_guards(route.take_guards())
264                .route(route),
265        )
266    }
267
268    /// Default service to be used if no matching resource could be found.
269    ///
270    /// If a default service is not registered, it will fall back to the default service of
271    /// the parent [`App`](crate::App) (see [`App::default_service`](crate::App::default_service)).
272    pub fn default_service<F, U>(mut self, f: F) -> Self
273    where
274        F: IntoServiceFactory<U, ServiceRequest>,
275        U: ServiceFactory<ServiceRequest, Config = (), Response = ServiceResponse, Error = Error>
276            + 'static,
277        U::InitError: fmt::Debug,
278    {
279        // create and configure default resource
280        self.default = Some(Rc::new(boxed::factory(f.into_factory().map_init_err(
281            |err| {
282                log::error!("Can not construct default service: {err:?}");
283            },
284        ))));
285
286        self
287    }
288
289    /// Registers a scope-wide middleware.
290    ///
291    /// `mw` is a middleware component (type), that can modify the request and response across all
292    /// sub-resources managed by this `Scope`.
293    ///
294    /// See [`App::wrap`](crate::App::wrap) for more details.
295    #[doc(alias = "middleware")]
296    #[doc(alias = "use")] // nodejs terminology
297    pub fn wrap<M, B>(
298        self,
299        mw: M,
300    ) -> Scope<
301        impl ServiceFactory<
302            ServiceRequest,
303            Config = (),
304            Response = ServiceResponse<B>,
305            Error = Error,
306            InitError = (),
307        >,
308    >
309    where
310        M: Transform<
311                T::Service,
312                ServiceRequest,
313                Response = ServiceResponse<B>,
314                Error = Error,
315                InitError = (),
316            > + 'static,
317        B: MessageBody,
318    {
319        Scope {
320            endpoint: apply(mw, self.endpoint),
321            rdef: self.rdef,
322            app_data: self.app_data,
323            guards: self.guards,
324            services: self.services,
325            default: self.default,
326            external: self.external,
327            factory_ref: self.factory_ref,
328        }
329    }
330
331    /// Registers a scope-wide function middleware.
332    ///
333    /// `mw` is a closure that runs during inbound and/or outbound processing in the request
334    /// life-cycle (request -> response), modifying request/response as necessary, across all
335    /// requests handled by the `Scope`.
336    ///
337    /// See [`App::wrap_fn`](crate::App::wrap_fn) for examples and more details.
338    #[doc(alias = "middleware")]
339    #[doc(alias = "use")] // nodejs terminology
340    pub fn wrap_fn<F, R, B>(
341        self,
342        mw: F,
343    ) -> Scope<
344        impl ServiceFactory<
345            ServiceRequest,
346            Config = (),
347            Response = ServiceResponse<B>,
348            Error = Error,
349            InitError = (),
350        >,
351    >
352    where
353        F: Fn(ServiceRequest, &T::Service) -> R + Clone + 'static,
354        R: Future<Output = Result<ServiceResponse<B>, Error>>,
355        B: MessageBody,
356    {
357        Scope {
358            endpoint: apply_fn_factory(self.endpoint, mw),
359            rdef: self.rdef,
360            app_data: self.app_data,
361            guards: self.guards,
362            services: self.services,
363            default: self.default,
364            external: self.external,
365            factory_ref: self.factory_ref,
366        }
367    }
368}
369
370impl<T, B> HttpServiceFactory for Scope<T>
371where
372    T: ServiceFactory<
373            ServiceRequest,
374            Config = (),
375            Response = ServiceResponse<B>,
376            Error = Error,
377            InitError = (),
378        > + 'static,
379    B: MessageBody + 'static,
380{
381    fn register(mut self, config: &mut AppService) {
382        // update default resource if needed
383        let default = self.default.unwrap_or_else(|| config.default_service());
384
385        // register nested services
386        let mut cfg = config.clone_config();
387        self.services
388            .into_iter()
389            .for_each(|mut srv| srv.register(&mut cfg));
390
391        let mut rmap = ResourceMap::new(ResourceDef::root_prefix(&self.rdef));
392
393        // external resources
394        for mut rdef in mem::take(&mut self.external) {
395            rmap.add(&mut rdef, None);
396        }
397
398        // complete scope pipeline creation
399        *self.factory_ref.borrow_mut() = Some(ScopeFactory {
400            default,
401            services: cfg
402                .into_services()
403                .1
404                .into_iter()
405                .map(|(mut rdef, srv, guards, nested)| {
406                    rmap.add(&mut rdef, nested);
407                    (rdef, srv, RefCell::new(guards))
408                })
409                .collect::<Vec<_>>()
410                .into_boxed_slice()
411                .into(),
412        });
413
414        // get guards
415        let guards = if self.guards.is_empty() {
416            None
417        } else {
418            Some(self.guards)
419        };
420
421        let scope_data = self.app_data.map(Rc::new);
422
423        // wraps endpoint service (including middleware) call and injects app data for this scope
424        let endpoint = apply_fn_factory(self.endpoint, move |mut req: ServiceRequest, srv| {
425            if let Some(ref data) = scope_data {
426                req.add_data_container(Rc::clone(data));
427            }
428
429            let fut = srv.call(req);
430
431            async { Ok(fut.await?.map_into_boxed_body()) }
432        });
433
434        // register final service
435        config.register_service(
436            ResourceDef::root_prefix(&self.rdef),
437            guards,
438            endpoint,
439            Some(Rc::new(rmap)),
440        )
441    }
442}
443
444pub struct ScopeFactory {
445    #[allow(clippy::type_complexity)]
446    services: Rc<
447        [(
448            ResourceDef,
449            BoxedHttpServiceFactory,
450            RefCell<Option<Guards>>,
451        )],
452    >,
453    default: Rc<BoxedHttpServiceFactory>,
454}
455
456impl ServiceFactory<ServiceRequest> for ScopeFactory {
457    type Response = ServiceResponse;
458    type Error = Error;
459    type Config = ();
460    type Service = ScopeService;
461    type InitError = ();
462    type Future = LocalBoxFuture<'static, Result<Self::Service, Self::InitError>>;
463
464    fn new_service(&self, _: ()) -> Self::Future {
465        // construct default service factory future
466        let default_fut = self.default.new_service(());
467
468        // construct all services factory future with it's resource def and guards.
469        let factory_fut = join_all(self.services.iter().map(|(path, factory, guards)| {
470            let path = path.clone();
471            let guards = guards.borrow_mut().take().unwrap_or_default();
472            let factory_fut = factory.new_service(());
473            async move {
474                factory_fut
475                    .await
476                    .map(move |service| (path, guards, service))
477            }
478        }));
479
480        Box::pin(async move {
481            let default = default_fut.await?;
482
483            // build router from the factory future result.
484            let router = factory_fut
485                .await
486                .into_iter()
487                .collect::<Result<Vec<_>, _>>()?
488                .drain(..)
489                .fold(Router::build(), |mut router, (path, guards, service)| {
490                    router.push(path, service, guards);
491                    router
492                })
493                .finish();
494
495            Ok(ScopeService { router, default })
496        })
497    }
498}
499
500pub struct ScopeService {
501    router: Router<BoxedHttpService, Vec<Box<dyn Guard>>>,
502    default: BoxedHttpService,
503}
504
505impl Service<ServiceRequest> for ScopeService {
506    type Response = ServiceResponse;
507    type Error = Error;
508    type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;
509
510    actix_service::always_ready!();
511
512    fn call(&self, mut req: ServiceRequest) -> Self::Future {
513        let res = self.router.recognize_fn(&mut req, |req, guards| {
514            let guard_ctx = req.guard_ctx();
515            guards.iter().all(|guard| guard.check(&guard_ctx))
516        });
517
518        if let Some((srv, _info)) = res {
519            srv.call(req)
520        } else {
521            self.default.call(req)
522        }
523    }
524}
525
526#[doc(hidden)]
527pub struct ScopeEndpoint {
528    factory: Rc<RefCell<Option<ScopeFactory>>>,
529}
530
531impl ScopeEndpoint {
532    fn new(factory: Rc<RefCell<Option<ScopeFactory>>>) -> Self {
533        ScopeEndpoint { factory }
534    }
535}
536
537impl ServiceFactory<ServiceRequest> for ScopeEndpoint {
538    type Response = ServiceResponse;
539    type Error = Error;
540    type Config = ();
541    type Service = ScopeService;
542    type InitError = ();
543    type Future = LocalBoxFuture<'static, Result<Self::Service, Self::InitError>>;
544
545    fn new_service(&self, _: ()) -> Self::Future {
546        self.factory.borrow_mut().as_mut().unwrap().new_service(())
547    }
548}
549
550#[cfg(test)]
551mod tests {
552    use actix_utils::future::ok;
553    use bytes::Bytes;
554
555    use super::*;
556    use crate::{
557        guard,
558        http::{
559            header::{self, HeaderValue},
560            Method, StatusCode,
561        },
562        middleware::DefaultHeaders,
563        test::{assert_body_eq, call_service, init_service, read_body, TestRequest},
564        web, App, HttpMessage, HttpRequest, HttpResponse,
565    };
566
567    #[test]
568    fn can_be_returned_from_fn() {
569        fn my_scope_1() -> Scope {
570            web::scope("/test")
571                .service(web::resource("").route(web::get().to(|| async { "hello" })))
572        }
573
574        fn my_scope_2() -> Scope<
575            impl ServiceFactory<
576                ServiceRequest,
577                Config = (),
578                Response = ServiceResponse<impl MessageBody>,
579                Error = Error,
580                InitError = (),
581            >,
582        > {
583            web::scope("/test-compat")
584                .wrap_fn(|req, srv| {
585                    let fut = srv.call(req);
586                    async { Ok(fut.await?.map_into_right_body::<()>()) }
587                })
588                .service(web::resource("").route(web::get().to(|| async { "hello" })))
589        }
590
591        fn my_scope_3() -> impl HttpServiceFactory {
592            my_scope_2()
593        }
594
595        App::new()
596            .service(my_scope_1())
597            .service(my_scope_2())
598            .service(my_scope_3());
599    }
600
601    #[actix_rt::test]
602    async fn test_scope() {
603        let srv = init_service(
604            App::new()
605                .service(web::scope("/app").service(web::resource("/path1").to(HttpResponse::Ok))),
606        )
607        .await;
608
609        let req = TestRequest::with_uri("/app/path1").to_request();
610        let resp = srv.call(req).await.unwrap();
611        assert_eq!(resp.status(), StatusCode::OK);
612    }
613
614    #[actix_rt::test]
615    async fn test_scope_root() {
616        let srv = init_service(
617            App::new().service(
618                web::scope("/app")
619                    .service(web::resource("").to(HttpResponse::Ok))
620                    .service(web::resource("/").to(HttpResponse::Created)),
621            ),
622        )
623        .await;
624
625        let req = TestRequest::with_uri("/app").to_request();
626        let resp = srv.call(req).await.unwrap();
627        assert_eq!(resp.status(), StatusCode::OK);
628
629        let req = TestRequest::with_uri("/app/").to_request();
630        let resp = srv.call(req).await.unwrap();
631        assert_eq!(resp.status(), StatusCode::CREATED);
632    }
633
634    #[actix_rt::test]
635    async fn test_scope_root2() {
636        let srv = init_service(
637            App::new().service(web::scope("/app/").service(web::resource("").to(HttpResponse::Ok))),
638        )
639        .await;
640
641        let req = TestRequest::with_uri("/app").to_request();
642        let resp = srv.call(req).await.unwrap();
643        assert_eq!(resp.status(), StatusCode::NOT_FOUND);
644
645        let req = TestRequest::with_uri("/app/").to_request();
646        let resp = srv.call(req).await.unwrap();
647        assert_eq!(resp.status(), StatusCode::OK);
648    }
649
650    #[actix_rt::test]
651    async fn test_scope_root3() {
652        let srv = init_service(
653            App::new()
654                .service(web::scope("/app/").service(web::resource("/").to(HttpResponse::Ok))),
655        )
656        .await;
657
658        let req = TestRequest::with_uri("/app").to_request();
659        let resp = srv.call(req).await.unwrap();
660        assert_eq!(resp.status(), StatusCode::NOT_FOUND);
661
662        let req = TestRequest::with_uri("/app/").to_request();
663        let resp = srv.call(req).await.unwrap();
664        assert_eq!(resp.status(), StatusCode::NOT_FOUND);
665    }
666
667    #[actix_rt::test]
668    async fn test_scope_route() {
669        let srv = init_service(
670            App::new().service(
671                web::scope("app")
672                    .route("/path1", web::get().to(HttpResponse::Ok))
673                    .route("/path1", web::delete().to(HttpResponse::Ok)),
674            ),
675        )
676        .await;
677
678        let req = TestRequest::with_uri("/app/path1").to_request();
679        let resp = srv.call(req).await.unwrap();
680        assert_eq!(resp.status(), StatusCode::OK);
681
682        let req = TestRequest::with_uri("/app/path1")
683            .method(Method::DELETE)
684            .to_request();
685        let resp = srv.call(req).await.unwrap();
686        assert_eq!(resp.status(), StatusCode::OK);
687
688        let req = TestRequest::with_uri("/app/path1")
689            .method(Method::POST)
690            .to_request();
691        let resp = srv.call(req).await.unwrap();
692        assert_eq!(resp.status(), StatusCode::NOT_FOUND);
693    }
694
695    #[actix_rt::test]
696    async fn test_scope_route_without_leading_slash() {
697        let srv = init_service(
698            App::new().service(
699                web::scope("app").service(
700                    web::resource("path1")
701                        .route(web::get().to(HttpResponse::Ok))
702                        .route(web::delete().to(HttpResponse::Ok)),
703                ),
704            ),
705        )
706        .await;
707
708        let req = TestRequest::with_uri("/app/path1").to_request();
709        let resp = srv.call(req).await.unwrap();
710        assert_eq!(resp.status(), StatusCode::OK);
711
712        let req = TestRequest::with_uri("/app/path1")
713            .method(Method::DELETE)
714            .to_request();
715        let resp = srv.call(req).await.unwrap();
716        assert_eq!(resp.status(), StatusCode::OK);
717
718        let req = TestRequest::with_uri("/app/path1")
719            .method(Method::POST)
720            .to_request();
721        let resp = srv.call(req).await.unwrap();
722        assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED);
723    }
724
725    #[actix_rt::test]
726    async fn test_scope_guard() {
727        let srv = init_service(
728            App::new().service(
729                web::scope("/app")
730                    .guard(guard::Get())
731                    .service(web::resource("/path1").to(HttpResponse::Ok)),
732            ),
733        )
734        .await;
735
736        let req = TestRequest::with_uri("/app/path1")
737            .method(Method::POST)
738            .to_request();
739        let resp = srv.call(req).await.unwrap();
740        assert_eq!(resp.status(), StatusCode::NOT_FOUND);
741
742        let req = TestRequest::with_uri("/app/path1")
743            .method(Method::GET)
744            .to_request();
745        let resp = srv.call(req).await.unwrap();
746        assert_eq!(resp.status(), StatusCode::OK);
747    }
748
749    #[actix_rt::test]
750    async fn test_scope_variable_segment() {
751        let srv = init_service(App::new().service(web::scope("/ab-{project}").service(
752            web::resource("/path1").to(|r: HttpRequest| {
753                HttpResponse::Ok().body(format!("project: {}", &r.match_info()["project"]))
754            }),
755        )))
756        .await;
757
758        let req = TestRequest::with_uri("/ab-project1/path1").to_request();
759        let res = srv.call(req).await.unwrap();
760        assert_eq!(res.status(), StatusCode::OK);
761        assert_body_eq!(res, b"project: project1");
762
763        let req = TestRequest::with_uri("/aa-project1/path1").to_request();
764        let res = srv.call(req).await.unwrap();
765        assert_eq!(res.status(), StatusCode::NOT_FOUND);
766    }
767
768    #[actix_rt::test]
769    async fn test_nested_scope() {
770        let srv = init_service(App::new().service(web::scope("/app").service(
771            web::scope("/t1").service(web::resource("/path1").to(HttpResponse::Created)),
772        )))
773        .await;
774
775        let req = TestRequest::with_uri("/app/t1/path1").to_request();
776        let resp = srv.call(req).await.unwrap();
777        assert_eq!(resp.status(), StatusCode::CREATED);
778    }
779
780    #[actix_rt::test]
781    async fn test_nested_scope_no_slash() {
782        let srv =
783            init_service(App::new().service(web::scope("/app").service(
784                web::scope("t1").service(web::resource("/path1").to(HttpResponse::Created)),
785            )))
786            .await;
787
788        let req = TestRequest::with_uri("/app/t1/path1").to_request();
789        let resp = srv.call(req).await.unwrap();
790        assert_eq!(resp.status(), StatusCode::CREATED);
791    }
792
793    #[actix_rt::test]
794    async fn test_nested_scope_root() {
795        let srv = init_service(
796            App::new().service(
797                web::scope("/app").service(
798                    web::scope("/t1")
799                        .service(web::resource("").to(HttpResponse::Ok))
800                        .service(web::resource("/").to(HttpResponse::Created)),
801                ),
802            ),
803        )
804        .await;
805
806        let req = TestRequest::with_uri("/app/t1").to_request();
807        let resp = srv.call(req).await.unwrap();
808        assert_eq!(resp.status(), StatusCode::OK);
809
810        let req = TestRequest::with_uri("/app/t1/").to_request();
811        let resp = srv.call(req).await.unwrap();
812        assert_eq!(resp.status(), StatusCode::CREATED);
813    }
814
815    #[actix_rt::test]
816    async fn test_nested_scope_filter() {
817        let srv = init_service(
818            App::new().service(
819                web::scope("/app").service(
820                    web::scope("/t1")
821                        .guard(guard::Get())
822                        .service(web::resource("/path1").to(HttpResponse::Ok)),
823                ),
824            ),
825        )
826        .await;
827
828        let req = TestRequest::with_uri("/app/t1/path1")
829            .method(Method::POST)
830            .to_request();
831        let resp = srv.call(req).await.unwrap();
832        assert_eq!(resp.status(), StatusCode::NOT_FOUND);
833
834        let req = TestRequest::with_uri("/app/t1/path1")
835            .method(Method::GET)
836            .to_request();
837        let resp = srv.call(req).await.unwrap();
838        assert_eq!(resp.status(), StatusCode::OK);
839    }
840
841    #[actix_rt::test]
842    async fn test_nested_scope_with_variable_segment() {
843        let srv = init_service(App::new().service(web::scope("/app").service(
844            web::scope("/{project_id}").service(web::resource("/path1").to(|r: HttpRequest| {
845                HttpResponse::Created().body(format!("project: {}", &r.match_info()["project_id"]))
846            })),
847        )))
848        .await;
849
850        let req = TestRequest::with_uri("/app/project_1/path1").to_request();
851        let res = srv.call(req).await.unwrap();
852        assert_eq!(res.status(), StatusCode::CREATED);
853        assert_body_eq!(res, b"project: project_1");
854    }
855
856    #[actix_rt::test]
857    async fn test_nested2_scope_with_variable_segment() {
858        let srv = init_service(App::new().service(web::scope("/app").service(
859            web::scope("/{project}").service(web::scope("/{id}").service(
860                web::resource("/path1").to(|r: HttpRequest| {
861                    HttpResponse::Created().body(format!(
862                        "project: {} - {}",
863                        &r.match_info()["project"],
864                        &r.match_info()["id"],
865                    ))
866                }),
867            )),
868        )))
869        .await;
870
871        let req = TestRequest::with_uri("/app/test/1/path1").to_request();
872        let res = srv.call(req).await.unwrap();
873        assert_eq!(res.status(), StatusCode::CREATED);
874        assert_body_eq!(res, b"project: test - 1");
875
876        let req = TestRequest::with_uri("/app/test/1/path2").to_request();
877        let res = srv.call(req).await.unwrap();
878        assert_eq!(res.status(), StatusCode::NOT_FOUND);
879    }
880
881    #[actix_rt::test]
882    async fn test_default_resource() {
883        let srv = init_service(
884            App::new().service(
885                web::scope("/app")
886                    .service(web::resource("/path1").to(HttpResponse::Ok))
887                    .default_service(|r: ServiceRequest| {
888                        ok(r.into_response(HttpResponse::BadRequest()))
889                    }),
890            ),
891        )
892        .await;
893
894        let req = TestRequest::with_uri("/app/path2").to_request();
895        let resp = srv.call(req).await.unwrap();
896        assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
897
898        let req = TestRequest::with_uri("/path2").to_request();
899        let resp = srv.call(req).await.unwrap();
900        assert_eq!(resp.status(), StatusCode::NOT_FOUND);
901    }
902
903    #[actix_rt::test]
904    async fn test_default_resource_propagation() {
905        let srv = init_service(
906            App::new()
907                .service(web::scope("/app1").default_service(web::to(HttpResponse::BadRequest)))
908                .service(web::scope("/app2"))
909                .default_service(|r: ServiceRequest| {
910                    ok(r.into_response(HttpResponse::MethodNotAllowed()))
911                }),
912        )
913        .await;
914
915        let req = TestRequest::with_uri("/non-exist").to_request();
916        let resp = srv.call(req).await.unwrap();
917        assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED);
918
919        let req = TestRequest::with_uri("/app1/non-exist").to_request();
920        let resp = srv.call(req).await.unwrap();
921        assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
922
923        let req = TestRequest::with_uri("/app2/non-exist").to_request();
924        let resp = srv.call(req).await.unwrap();
925        assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED);
926    }
927
928    #[actix_rt::test]
929    async fn test_middleware() {
930        let srv = init_service(
931            App::new().service(
932                web::scope("app")
933                    .wrap(
934                        DefaultHeaders::new()
935                            .add((header::CONTENT_TYPE, HeaderValue::from_static("0001"))),
936                    )
937                    .service(web::resource("/test").route(web::get().to(HttpResponse::Ok))),
938            ),
939        )
940        .await;
941
942        let req = TestRequest::with_uri("/app/test").to_request();
943        let resp = call_service(&srv, req).await;
944        assert_eq!(resp.status(), StatusCode::OK);
945        assert_eq!(
946            resp.headers().get(header::CONTENT_TYPE).unwrap(),
947            HeaderValue::from_static("0001")
948        );
949    }
950
951    #[actix_rt::test]
952    async fn test_middleware_body_type() {
953        // Compile test that Scope accepts any body type; test for `EitherBody`
954        let srv = init_service(
955            App::new().service(
956                web::scope("app")
957                    .wrap_fn(|req, srv| {
958                        let fut = srv.call(req);
959                        async { Ok(fut.await?.map_into_right_body::<()>()) }
960                    })
961                    .service(web::resource("/test").route(web::get().to(|| async { "hello" }))),
962            ),
963        )
964        .await;
965
966        // test if `MessageBody::try_into_bytes()` is preserved across scope layer
967        use actix_http::body::MessageBody as _;
968        let req = TestRequest::with_uri("/app/test").to_request();
969        let resp = call_service(&srv, req).await;
970        let body = resp.into_body();
971        assert_eq!(body.try_into_bytes().unwrap(), b"hello".as_ref());
972    }
973
974    #[actix_rt::test]
975    async fn test_middleware_fn() {
976        let srv = init_service(
977            App::new().service(
978                web::scope("app")
979                    .wrap_fn(|req, srv| {
980                        let fut = srv.call(req);
981                        async move {
982                            let mut res = fut.await?;
983                            res.headers_mut()
984                                .insert(header::CONTENT_TYPE, HeaderValue::from_static("0001"));
985                            Ok(res)
986                        }
987                    })
988                    .route("/test", web::get().to(HttpResponse::Ok)),
989            ),
990        )
991        .await;
992
993        let req = TestRequest::with_uri("/app/test").to_request();
994        let resp = call_service(&srv, req).await;
995        assert_eq!(resp.status(), StatusCode::OK);
996        assert_eq!(
997            resp.headers().get(header::CONTENT_TYPE).unwrap(),
998            HeaderValue::from_static("0001")
999        );
1000    }
1001
1002    #[actix_rt::test]
1003    async fn test_middleware_app_data() {
1004        let srv = init_service(
1005            App::new().service(
1006                web::scope("app")
1007                    .app_data(1usize)
1008                    .wrap_fn(|req, srv| {
1009                        assert_eq!(req.app_data::<usize>(), Some(&1usize));
1010                        req.extensions_mut().insert(1usize);
1011                        srv.call(req)
1012                    })
1013                    .route("/test", web::get().to(HttpResponse::Ok))
1014                    .default_service(|req: ServiceRequest| async move {
1015                        let (req, _) = req.into_parts();
1016
1017                        assert_eq!(req.extensions().get::<usize>(), Some(&1));
1018
1019                        Ok(ServiceResponse::new(
1020                            req,
1021                            HttpResponse::BadRequest().finish(),
1022                        ))
1023                    }),
1024            ),
1025        )
1026        .await;
1027
1028        let req = TestRequest::with_uri("/app/test").to_request();
1029        let resp = call_service(&srv, req).await;
1030        assert_eq!(resp.status(), StatusCode::OK);
1031
1032        let req = TestRequest::with_uri("/app/default").to_request();
1033        let resp = call_service(&srv, req).await;
1034        assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
1035    }
1036
1037    // allow deprecated {App, Scope}::data
1038    #[allow(deprecated)]
1039    #[actix_rt::test]
1040    async fn test_override_data() {
1041        let srv = init_service(App::new().data(1usize).service(
1042            web::scope("app").data(10usize).route(
1043                "/t",
1044                web::get().to(|data: web::Data<usize>| {
1045                    assert_eq!(**data, 10);
1046                    HttpResponse::Ok()
1047                }),
1048            ),
1049        ))
1050        .await;
1051
1052        let req = TestRequest::with_uri("/app/t").to_request();
1053        let resp = call_service(&srv, req).await;
1054        assert_eq!(resp.status(), StatusCode::OK);
1055    }
1056
1057    // allow deprecated `{App, Scope}::data`
1058    #[allow(deprecated)]
1059    #[actix_rt::test]
1060    async fn test_override_data_default_service() {
1061        let srv =
1062            init_service(App::new().data(1usize).service(
1063                web::scope("app").data(10usize).default_service(web::to(
1064                    |data: web::Data<usize>| {
1065                        assert_eq!(**data, 10);
1066                        HttpResponse::Ok()
1067                    },
1068                )),
1069            ))
1070            .await;
1071
1072        let req = TestRequest::with_uri("/app/t").to_request();
1073        let resp = call_service(&srv, req).await;
1074        assert_eq!(resp.status(), StatusCode::OK);
1075    }
1076
1077    #[actix_rt::test]
1078    async fn test_override_app_data() {
1079        let srv = init_service(App::new().app_data(web::Data::new(1usize)).service(
1080            web::scope("app").app_data(web::Data::new(10usize)).route(
1081                "/t",
1082                web::get().to(|data: web::Data<usize>| {
1083                    assert_eq!(**data, 10);
1084                    HttpResponse::Ok()
1085                }),
1086            ),
1087        ))
1088        .await;
1089
1090        let req = TestRequest::with_uri("/app/t").to_request();
1091        let resp = call_service(&srv, req).await;
1092        assert_eq!(resp.status(), StatusCode::OK);
1093    }
1094
1095    #[actix_rt::test]
1096    async fn test_scope_config() {
1097        let srv = init_service(App::new().service(web::scope("/app").configure(|s| {
1098            s.route("/path1", web::get().to(HttpResponse::Ok));
1099        })))
1100        .await;
1101
1102        let req = TestRequest::with_uri("/app/path1").to_request();
1103        let resp = srv.call(req).await.unwrap();
1104        assert_eq!(resp.status(), StatusCode::OK);
1105    }
1106
1107    #[actix_rt::test]
1108    async fn test_scope_config_2() {
1109        let srv = init_service(App::new().service(web::scope("/app").configure(|s| {
1110            s.service(web::scope("/v1").configure(|s| {
1111                s.route("/", web::get().to(HttpResponse::Ok));
1112            }));
1113        })))
1114        .await;
1115
1116        let req = TestRequest::with_uri("/app/v1/").to_request();
1117        let resp = srv.call(req).await.unwrap();
1118        assert_eq!(resp.status(), StatusCode::OK);
1119    }
1120
1121    #[actix_rt::test]
1122    async fn test_url_for_external() {
1123        let srv = init_service(App::new().service(web::scope("/app").configure(|s| {
1124            s.service(web::scope("/v1").configure(|s| {
1125                s.external_resource("youtube", "https://youtube.com/watch/{video_id}");
1126                s.route(
1127                    "/",
1128                    web::get().to(|req: HttpRequest| {
1129                        HttpResponse::Ok()
1130                            .body(req.url_for("youtube", ["xxxxxx"]).unwrap().to_string())
1131                    }),
1132                );
1133            }));
1134        })))
1135        .await;
1136
1137        let req = TestRequest::with_uri("/app/v1/").to_request();
1138        let resp = srv.call(req).await.unwrap();
1139        assert_eq!(resp.status(), StatusCode::OK);
1140        let body = read_body(resp).await;
1141        assert_eq!(body, &b"https://youtube.com/watch/xxxxxx"[..]);
1142    }
1143
1144    #[actix_rt::test]
1145    async fn test_url_for_nested() {
1146        let srv = init_service(App::new().service(web::scope("/a").service(
1147            web::scope("/b").service(web::resource("/c/{stuff}").name("c").route(web::get().to(
1148                |req: HttpRequest| {
1149                    HttpResponse::Ok().body(format!("{}", req.url_for("c", ["12345"]).unwrap()))
1150                },
1151            ))),
1152        )))
1153        .await;
1154
1155        let req = TestRequest::with_uri("/a/b/c/test").to_request();
1156        let resp = call_service(&srv, req).await;
1157        assert_eq!(resp.status(), StatusCode::OK);
1158        let body = read_body(resp).await;
1159        assert_eq!(
1160            body,
1161            Bytes::from_static(b"http://localhost:8080/a/b/c/12345")
1162        );
1163    }
1164
1165    #[actix_rt::test]
1166    async fn dynamic_scopes() {
1167        let srv = init_service(
1168            App::new().service(
1169                web::scope("/{a}/").service(
1170                    web::scope("/{b}/")
1171                        .route("", web::get().to(|_: HttpRequest| HttpResponse::Created()))
1172                        .route(
1173                            "/",
1174                            web::get().to(|_: HttpRequest| HttpResponse::Accepted()),
1175                        )
1176                        .route("/{c}", web::get().to(|_: HttpRequest| HttpResponse::Ok())),
1177                ),
1178            ),
1179        )
1180        .await;
1181
1182        // note the unintuitive behavior with trailing slashes on scopes with dynamic segments
1183        let req = TestRequest::with_uri("/a//b//c").to_request();
1184        let resp = call_service(&srv, req).await;
1185        assert_eq!(resp.status(), StatusCode::OK);
1186
1187        let req = TestRequest::with_uri("/a//b/").to_request();
1188        let resp = call_service(&srv, req).await;
1189        assert_eq!(resp.status(), StatusCode::CREATED);
1190
1191        let req = TestRequest::with_uri("/a//b//").to_request();
1192        let resp = call_service(&srv, req).await;
1193        assert_eq!(resp.status(), StatusCode::ACCEPTED);
1194
1195        let req = TestRequest::with_uri("/a//b//c/d").to_request();
1196        let resp = call_service(&srv, req).await;
1197        assert_eq!(resp.status(), StatusCode::NOT_FOUND);
1198
1199        let srv = init_service(
1200            App::new().service(
1201                web::scope("/{a}").service(
1202                    web::scope("/{b}")
1203                        .route("", web::get().to(|_: HttpRequest| HttpResponse::Created()))
1204                        .route(
1205                            "/",
1206                            web::get().to(|_: HttpRequest| HttpResponse::Accepted()),
1207                        )
1208                        .route("/{c}", web::get().to(|_: HttpRequest| HttpResponse::Ok())),
1209                ),
1210            ),
1211        )
1212        .await;
1213
1214        let req = TestRequest::with_uri("/a/b/c").to_request();
1215        let resp = call_service(&srv, req).await;
1216        assert_eq!(resp.status(), StatusCode::OK);
1217
1218        let req = TestRequest::with_uri("/a/b").to_request();
1219        let resp = call_service(&srv, req).await;
1220        assert_eq!(resp.status(), StatusCode::CREATED);
1221
1222        let req = TestRequest::with_uri("/a/b/").to_request();
1223        let resp = call_service(&srv, req).await;
1224        assert_eq!(resp.status(), StatusCode::ACCEPTED);
1225
1226        let req = TestRequest::with_uri("/a/b/c/d").to_request();
1227        let resp = call_service(&srv, req).await;
1228        assert_eq!(resp.status(), StatusCode::NOT_FOUND);
1229    }
1230}