Skip to main content

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
388        // Update the prefix for the nested scope
389        #[cfg(feature = "experimental-introspection")]
390        {
391            let scope_id = config.prepare_scope_id();
392            cfg.scope_id_stack.push(scope_id);
393            cfg.update_prefix(&self.rdef);
394        }
395
396        self.services
397            .into_iter()
398            .for_each(|mut srv| srv.register(&mut cfg));
399
400        let mut rmap = ResourceMap::new(ResourceDef::root_prefix(&self.rdef));
401
402        #[cfg(feature = "experimental-introspection")]
403        let origin_scope = cfg.current_prefix.clone();
404
405        // external resources
406        for mut rdef in mem::take(&mut self.external) {
407            #[cfg(feature = "experimental-introspection")]
408            {
409                cfg.introspector
410                    .borrow_mut()
411                    .register_external(&rdef, &origin_scope);
412            }
413            rmap.add(&mut rdef, None);
414        }
415
416        // complete scope pipeline creation
417        *self.factory_ref.borrow_mut() = Some(ScopeFactory {
418            default,
419            services: cfg
420                .into_services()
421                .1
422                .into_iter()
423                .map(|(mut rdef, srv, guards, nested)| {
424                    rmap.add(&mut rdef, nested);
425                    (rdef, srv, RefCell::new(guards))
426                })
427                .collect::<Vec<_>>()
428                .into_boxed_slice()
429                .into(),
430        });
431
432        // get guards
433        let guards = if self.guards.is_empty() {
434            None
435        } else {
436            Some(self.guards)
437        };
438
439        let scope_data = self.app_data.map(Rc::new);
440
441        // wraps endpoint service (including middleware) call and injects app data for this scope
442        let endpoint = apply_fn_factory(self.endpoint, move |mut req: ServiceRequest, srv| {
443            if let Some(ref data) = scope_data {
444                req.add_data_container(Rc::clone(data));
445            }
446
447            let fut = srv.call(req);
448
449            async { Ok(fut.await?.map_into_boxed_body()) }
450        });
451
452        // register final service
453        config.register_service(
454            ResourceDef::root_prefix(&self.rdef),
455            guards,
456            endpoint,
457            Some(Rc::new(rmap)),
458        )
459    }
460}
461
462pub struct ScopeFactory {
463    #[allow(clippy::type_complexity)]
464    services: Rc<
465        [(
466            ResourceDef,
467            BoxedHttpServiceFactory,
468            RefCell<Option<Guards>>,
469        )],
470    >,
471    default: Rc<BoxedHttpServiceFactory>,
472}
473
474impl ServiceFactory<ServiceRequest> for ScopeFactory {
475    type Response = ServiceResponse;
476    type Error = Error;
477    type Config = ();
478    type Service = ScopeService;
479    type InitError = ();
480    type Future = LocalBoxFuture<'static, Result<Self::Service, Self::InitError>>;
481
482    fn new_service(&self, _: ()) -> Self::Future {
483        // construct default service factory future
484        let default_fut = self.default.new_service(());
485
486        // construct all services factory future with it's resource def and guards.
487        let factory_fut = join_all(self.services.iter().map(|(path, factory, guards)| {
488            let path = path.clone();
489            let guards = guards.borrow_mut().take().unwrap_or_default();
490            let factory_fut = factory.new_service(());
491            async move {
492                factory_fut
493                    .await
494                    .map(move |service| (path, guards, service))
495            }
496        }));
497
498        Box::pin(async move {
499            let default = default_fut.await?;
500
501            // build router from the factory future result.
502            let router = factory_fut
503                .await
504                .into_iter()
505                .collect::<Result<Vec<_>, _>>()?
506                .drain(..)
507                .fold(Router::build(), |mut router, (path, guards, service)| {
508                    router.push(path, service, guards);
509                    router
510                })
511                .finish();
512
513            Ok(ScopeService { router, default })
514        })
515    }
516}
517
518pub struct ScopeService {
519    router: Router<BoxedHttpService, Vec<Box<dyn Guard>>>,
520    default: BoxedHttpService,
521}
522
523impl Service<ServiceRequest> for ScopeService {
524    type Response = ServiceResponse;
525    type Error = Error;
526    type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;
527
528    actix_service::always_ready!();
529
530    fn call(&self, mut req: ServiceRequest) -> Self::Future {
531        let res = self.router.recognize_fn(&mut req, |req, guards| {
532            let guard_ctx = req.guard_ctx();
533            guards.iter().all(|guard| guard.check(&guard_ctx))
534        });
535
536        if let Some((srv, _info)) = res {
537            srv.call(req)
538        } else {
539            self.default.call(req)
540        }
541    }
542}
543
544#[doc(hidden)]
545pub struct ScopeEndpoint {
546    factory: Rc<RefCell<Option<ScopeFactory>>>,
547}
548
549impl ScopeEndpoint {
550    fn new(factory: Rc<RefCell<Option<ScopeFactory>>>) -> Self {
551        ScopeEndpoint { factory }
552    }
553}
554
555impl ServiceFactory<ServiceRequest> for ScopeEndpoint {
556    type Response = ServiceResponse;
557    type Error = Error;
558    type Config = ();
559    type Service = ScopeService;
560    type InitError = ();
561    type Future = LocalBoxFuture<'static, Result<Self::Service, Self::InitError>>;
562
563    fn new_service(&self, _: ()) -> Self::Future {
564        self.factory.borrow_mut().as_mut().unwrap().new_service(())
565    }
566}
567
568#[cfg(test)]
569mod tests {
570    use actix_utils::future::ok;
571    use bytes::Bytes;
572
573    use super::*;
574    use crate::{
575        guard,
576        http::{
577            header::{self, HeaderValue},
578            Method, StatusCode,
579        },
580        middleware::DefaultHeaders,
581        test::{assert_body_eq, call_service, init_service, read_body, TestRequest},
582        web, App, HttpMessage, HttpRequest, HttpResponse,
583    };
584
585    #[test]
586    fn can_be_returned_from_fn() {
587        fn my_scope_1() -> Scope {
588            web::scope("/test")
589                .service(web::resource("").route(web::get().to(|| async { "hello" })))
590        }
591
592        fn my_scope_2() -> Scope<
593            impl ServiceFactory<
594                ServiceRequest,
595                Config = (),
596                Response = ServiceResponse<impl MessageBody>,
597                Error = Error,
598                InitError = (),
599            >,
600        > {
601            web::scope("/test-compat")
602                .wrap_fn(|req, srv| {
603                    let fut = srv.call(req);
604                    async { Ok(fut.await?.map_into_right_body::<()>()) }
605                })
606                .service(web::resource("").route(web::get().to(|| async { "hello" })))
607        }
608
609        fn my_scope_3() -> impl HttpServiceFactory {
610            my_scope_2()
611        }
612
613        App::new()
614            .service(my_scope_1())
615            .service(my_scope_2())
616            .service(my_scope_3());
617    }
618
619    #[actix_rt::test]
620    async fn test_scope() {
621        let srv = init_service(
622            App::new()
623                .service(web::scope("/app").service(web::resource("/path1").to(HttpResponse::Ok))),
624        )
625        .await;
626
627        let req = TestRequest::with_uri("/app/path1").to_request();
628        let resp = srv.call(req).await.unwrap();
629        assert_eq!(resp.status(), StatusCode::OK);
630    }
631
632    #[actix_rt::test]
633    async fn test_scope_root() {
634        let srv = init_service(
635            App::new().service(
636                web::scope("/app")
637                    .service(web::resource("").to(HttpResponse::Ok))
638                    .service(web::resource("/").to(HttpResponse::Created)),
639            ),
640        )
641        .await;
642
643        let req = TestRequest::with_uri("/app").to_request();
644        let resp = srv.call(req).await.unwrap();
645        assert_eq!(resp.status(), StatusCode::OK);
646
647        let req = TestRequest::with_uri("/app/").to_request();
648        let resp = srv.call(req).await.unwrap();
649        assert_eq!(resp.status(), StatusCode::CREATED);
650    }
651
652    #[actix_rt::test]
653    async fn test_scope_root2() {
654        let srv = init_service(
655            App::new().service(web::scope("/app/").service(web::resource("").to(HttpResponse::Ok))),
656        )
657        .await;
658
659        let req = TestRequest::with_uri("/app").to_request();
660        let resp = srv.call(req).await.unwrap();
661        assert_eq!(resp.status(), StatusCode::NOT_FOUND);
662
663        let req = TestRequest::with_uri("/app/").to_request();
664        let resp = srv.call(req).await.unwrap();
665        assert_eq!(resp.status(), StatusCode::OK);
666    }
667
668    #[actix_rt::test]
669    async fn test_scope_root3() {
670        let srv = init_service(
671            App::new()
672                .service(web::scope("/app/").service(web::resource("/").to(HttpResponse::Ok))),
673        )
674        .await;
675
676        let req = TestRequest::with_uri("/app").to_request();
677        let resp = srv.call(req).await.unwrap();
678        assert_eq!(resp.status(), StatusCode::NOT_FOUND);
679
680        let req = TestRequest::with_uri("/app/").to_request();
681        let resp = srv.call(req).await.unwrap();
682        assert_eq!(resp.status(), StatusCode::NOT_FOUND);
683    }
684
685    #[actix_rt::test]
686    async fn test_scope_route() {
687        let srv = init_service(
688            App::new().service(
689                web::scope("app")
690                    .route("/path1", web::get().to(HttpResponse::Ok))
691                    .route("/path1", web::delete().to(HttpResponse::Ok)),
692            ),
693        )
694        .await;
695
696        let req = TestRequest::with_uri("/app/path1").to_request();
697        let resp = srv.call(req).await.unwrap();
698        assert_eq!(resp.status(), StatusCode::OK);
699
700        let req = TestRequest::with_uri("/app/path1")
701            .method(Method::DELETE)
702            .to_request();
703        let resp = srv.call(req).await.unwrap();
704        assert_eq!(resp.status(), StatusCode::OK);
705
706        let req = TestRequest::with_uri("/app/path1")
707            .method(Method::POST)
708            .to_request();
709        let resp = srv.call(req).await.unwrap();
710        assert_eq!(resp.status(), StatusCode::NOT_FOUND);
711    }
712
713    #[actix_rt::test]
714    async fn test_scope_route_without_leading_slash() {
715        let srv = init_service(
716            App::new().service(
717                web::scope("app").service(
718                    web::resource("path1")
719                        .route(web::get().to(HttpResponse::Ok))
720                        .route(web::delete().to(HttpResponse::Ok)),
721                ),
722            ),
723        )
724        .await;
725
726        let req = TestRequest::with_uri("/app/path1").to_request();
727        let resp = srv.call(req).await.unwrap();
728        assert_eq!(resp.status(), StatusCode::OK);
729
730        let req = TestRequest::with_uri("/app/path1")
731            .method(Method::DELETE)
732            .to_request();
733        let resp = srv.call(req).await.unwrap();
734        assert_eq!(resp.status(), StatusCode::OK);
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::METHOD_NOT_ALLOWED);
741    }
742
743    #[actix_rt::test]
744    async fn test_scope_guard() {
745        let srv = init_service(
746            App::new().service(
747                web::scope("/app")
748                    .guard(guard::Get())
749                    .service(web::resource("/path1").to(HttpResponse::Ok)),
750            ),
751        )
752        .await;
753
754        let req = TestRequest::with_uri("/app/path1")
755            .method(Method::POST)
756            .to_request();
757        let resp = srv.call(req).await.unwrap();
758        assert_eq!(resp.status(), StatusCode::NOT_FOUND);
759
760        let req = TestRequest::with_uri("/app/path1")
761            .method(Method::GET)
762            .to_request();
763        let resp = srv.call(req).await.unwrap();
764        assert_eq!(resp.status(), StatusCode::OK);
765    }
766
767    #[actix_rt::test]
768    async fn test_scope_variable_segment() {
769        let srv = init_service(App::new().service(web::scope("/ab-{project}").service(
770            web::resource("/path1").to(|r: HttpRequest| {
771                HttpResponse::Ok().body(format!("project: {}", &r.match_info()["project"]))
772            }),
773        )))
774        .await;
775
776        let req = TestRequest::with_uri("/ab-project1/path1").to_request();
777        let res = srv.call(req).await.unwrap();
778        assert_eq!(res.status(), StatusCode::OK);
779        assert_body_eq!(res, b"project: project1");
780
781        let req = TestRequest::with_uri("/aa-project1/path1").to_request();
782        let res = srv.call(req).await.unwrap();
783        assert_eq!(res.status(), StatusCode::NOT_FOUND);
784    }
785
786    #[actix_rt::test]
787    async fn test_nested_scope() {
788        let srv = init_service(App::new().service(web::scope("/app").service(
789            web::scope("/t1").service(web::resource("/path1").to(HttpResponse::Created)),
790        )))
791        .await;
792
793        let req = TestRequest::with_uri("/app/t1/path1").to_request();
794        let resp = srv.call(req).await.unwrap();
795        assert_eq!(resp.status(), StatusCode::CREATED);
796    }
797
798    #[actix_rt::test]
799    async fn test_nested_scope_no_slash() {
800        let srv =
801            init_service(App::new().service(web::scope("/app").service(
802                web::scope("t1").service(web::resource("/path1").to(HttpResponse::Created)),
803            )))
804            .await;
805
806        let req = TestRequest::with_uri("/app/t1/path1").to_request();
807        let resp = srv.call(req).await.unwrap();
808        assert_eq!(resp.status(), StatusCode::CREATED);
809    }
810
811    #[actix_rt::test]
812    async fn test_nested_scope_root() {
813        let srv = init_service(
814            App::new().service(
815                web::scope("/app").service(
816                    web::scope("/t1")
817                        .service(web::resource("").to(HttpResponse::Ok))
818                        .service(web::resource("/").to(HttpResponse::Created)),
819                ),
820            ),
821        )
822        .await;
823
824        let req = TestRequest::with_uri("/app/t1").to_request();
825        let resp = srv.call(req).await.unwrap();
826        assert_eq!(resp.status(), StatusCode::OK);
827
828        let req = TestRequest::with_uri("/app/t1/").to_request();
829        let resp = srv.call(req).await.unwrap();
830        assert_eq!(resp.status(), StatusCode::CREATED);
831    }
832
833    #[actix_rt::test]
834    async fn test_nested_scope_filter() {
835        let srv = init_service(
836            App::new().service(
837                web::scope("/app").service(
838                    web::scope("/t1")
839                        .guard(guard::Get())
840                        .service(web::resource("/path1").to(HttpResponse::Ok)),
841                ),
842            ),
843        )
844        .await;
845
846        let req = TestRequest::with_uri("/app/t1/path1")
847            .method(Method::POST)
848            .to_request();
849        let resp = srv.call(req).await.unwrap();
850        assert_eq!(resp.status(), StatusCode::NOT_FOUND);
851
852        let req = TestRequest::with_uri("/app/t1/path1")
853            .method(Method::GET)
854            .to_request();
855        let resp = srv.call(req).await.unwrap();
856        assert_eq!(resp.status(), StatusCode::OK);
857    }
858
859    #[actix_rt::test]
860    async fn test_nested_scope_with_variable_segment() {
861        let srv = init_service(App::new().service(web::scope("/app").service(
862            web::scope("/{project_id}").service(web::resource("/path1").to(|r: HttpRequest| {
863                HttpResponse::Created().body(format!("project: {}", &r.match_info()["project_id"]))
864            })),
865        )))
866        .await;
867
868        let req = TestRequest::with_uri("/app/project_1/path1").to_request();
869        let res = srv.call(req).await.unwrap();
870        assert_eq!(res.status(), StatusCode::CREATED);
871        assert_body_eq!(res, b"project: project_1");
872    }
873
874    #[actix_rt::test]
875    async fn test_nested2_scope_with_variable_segment() {
876        let srv = init_service(App::new().service(web::scope("/app").service(
877            web::scope("/{project}").service(web::scope("/{id}").service(
878                web::resource("/path1").to(|r: HttpRequest| {
879                    HttpResponse::Created().body(format!(
880                        "project: {} - {}",
881                        &r.match_info()["project"],
882                        &r.match_info()["id"],
883                    ))
884                }),
885            )),
886        )))
887        .await;
888
889        let req = TestRequest::with_uri("/app/test/1/path1").to_request();
890        let res = srv.call(req).await.unwrap();
891        assert_eq!(res.status(), StatusCode::CREATED);
892        assert_body_eq!(res, b"project: test - 1");
893
894        let req = TestRequest::with_uri("/app/test/1/path2").to_request();
895        let res = srv.call(req).await.unwrap();
896        assert_eq!(res.status(), StatusCode::NOT_FOUND);
897    }
898
899    #[actix_rt::test]
900    async fn test_default_resource() {
901        let srv = init_service(
902            App::new().service(
903                web::scope("/app")
904                    .service(web::resource("/path1").to(HttpResponse::Ok))
905                    .default_service(|r: ServiceRequest| {
906                        ok(r.into_response(HttpResponse::BadRequest()))
907                    }),
908            ),
909        )
910        .await;
911
912        let req = TestRequest::with_uri("/app/path2").to_request();
913        let resp = srv.call(req).await.unwrap();
914        assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
915
916        let req = TestRequest::with_uri("/path2").to_request();
917        let resp = srv.call(req).await.unwrap();
918        assert_eq!(resp.status(), StatusCode::NOT_FOUND);
919    }
920
921    #[actix_rt::test]
922    async fn test_default_resource_propagation() {
923        let srv = init_service(
924            App::new()
925                .service(web::scope("/app1").default_service(web::to(HttpResponse::BadRequest)))
926                .service(web::scope("/app2"))
927                .default_service(|r: ServiceRequest| {
928                    ok(r.into_response(HttpResponse::MethodNotAllowed()))
929                }),
930        )
931        .await;
932
933        let req = TestRequest::with_uri("/non-exist").to_request();
934        let resp = srv.call(req).await.unwrap();
935        assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED);
936
937        let req = TestRequest::with_uri("/app1/non-exist").to_request();
938        let resp = srv.call(req).await.unwrap();
939        assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
940
941        let req = TestRequest::with_uri("/app2/non-exist").to_request();
942        let resp = srv.call(req).await.unwrap();
943        assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED);
944    }
945
946    #[actix_rt::test]
947    async fn test_middleware() {
948        let srv = init_service(
949            App::new().service(
950                web::scope("app")
951                    .wrap(
952                        DefaultHeaders::new()
953                            .add((header::CONTENT_TYPE, HeaderValue::from_static("0001"))),
954                    )
955                    .service(web::resource("/test").route(web::get().to(HttpResponse::Ok))),
956            ),
957        )
958        .await;
959
960        let req = TestRequest::with_uri("/app/test").to_request();
961        let resp = call_service(&srv, req).await;
962        assert_eq!(resp.status(), StatusCode::OK);
963        assert_eq!(
964            resp.headers().get(header::CONTENT_TYPE).unwrap(),
965            HeaderValue::from_static("0001")
966        );
967    }
968
969    #[actix_rt::test]
970    async fn test_middleware_body_type() {
971        // Compile test that Scope accepts any body type; test for `EitherBody`
972        let srv = init_service(
973            App::new().service(
974                web::scope("app")
975                    .wrap_fn(|req, srv| {
976                        let fut = srv.call(req);
977                        async { Ok(fut.await?.map_into_right_body::<()>()) }
978                    })
979                    .service(web::resource("/test").route(web::get().to(|| async { "hello" }))),
980            ),
981        )
982        .await;
983
984        // test if `MessageBody::try_into_bytes()` is preserved across scope layer
985        use actix_http::body::MessageBody as _;
986        let req = TestRequest::with_uri("/app/test").to_request();
987        let resp = call_service(&srv, req).await;
988        let body = resp.into_body();
989        assert_eq!(body.try_into_bytes().unwrap(), b"hello".as_ref());
990    }
991
992    #[actix_rt::test]
993    async fn test_middleware_fn() {
994        let srv = init_service(
995            App::new().service(
996                web::scope("app")
997                    .wrap_fn(|req, srv| {
998                        let fut = srv.call(req);
999                        async move {
1000                            let mut res = fut.await?;
1001                            res.headers_mut()
1002                                .insert(header::CONTENT_TYPE, HeaderValue::from_static("0001"));
1003                            Ok(res)
1004                        }
1005                    })
1006                    .route("/test", web::get().to(HttpResponse::Ok)),
1007            ),
1008        )
1009        .await;
1010
1011        let req = TestRequest::with_uri("/app/test").to_request();
1012        let resp = call_service(&srv, req).await;
1013        assert_eq!(resp.status(), StatusCode::OK);
1014        assert_eq!(
1015            resp.headers().get(header::CONTENT_TYPE).unwrap(),
1016            HeaderValue::from_static("0001")
1017        );
1018    }
1019
1020    #[actix_rt::test]
1021    async fn test_middleware_app_data() {
1022        let srv = init_service(
1023            App::new().service(
1024                web::scope("app")
1025                    .app_data(1usize)
1026                    .wrap_fn(|req, srv| {
1027                        assert_eq!(req.app_data::<usize>(), Some(&1usize));
1028                        req.extensions_mut().insert(1usize);
1029                        srv.call(req)
1030                    })
1031                    .route("/test", web::get().to(HttpResponse::Ok))
1032                    .default_service(|req: ServiceRequest| async move {
1033                        let (req, _) = req.into_parts();
1034
1035                        assert_eq!(req.extensions().get::<usize>(), Some(&1));
1036
1037                        Ok(ServiceResponse::new(
1038                            req,
1039                            HttpResponse::BadRequest().finish(),
1040                        ))
1041                    }),
1042            ),
1043        )
1044        .await;
1045
1046        let req = TestRequest::with_uri("/app/test").to_request();
1047        let resp = call_service(&srv, req).await;
1048        assert_eq!(resp.status(), StatusCode::OK);
1049
1050        let req = TestRequest::with_uri("/app/default").to_request();
1051        let resp = call_service(&srv, req).await;
1052        assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
1053    }
1054
1055    // allow deprecated {App, Scope}::data
1056    #[allow(deprecated)]
1057    #[actix_rt::test]
1058    async fn test_override_data() {
1059        let srv = init_service(App::new().data(1usize).service(
1060            web::scope("app").data(10usize).route(
1061                "/t",
1062                web::get().to(|data: web::Data<usize>| {
1063                    assert_eq!(**data, 10);
1064                    HttpResponse::Ok()
1065                }),
1066            ),
1067        ))
1068        .await;
1069
1070        let req = TestRequest::with_uri("/app/t").to_request();
1071        let resp = call_service(&srv, req).await;
1072        assert_eq!(resp.status(), StatusCode::OK);
1073    }
1074
1075    // allow deprecated `{App, Scope}::data`
1076    #[allow(deprecated)]
1077    #[actix_rt::test]
1078    async fn test_override_data_default_service() {
1079        let srv =
1080            init_service(App::new().data(1usize).service(
1081                web::scope("app").data(10usize).default_service(web::to(
1082                    |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_override_app_data() {
1097        let srv = init_service(App::new().app_data(web::Data::new(1usize)).service(
1098            web::scope("app").app_data(web::Data::new(10usize)).route(
1099                "/t",
1100                web::get().to(|data: web::Data<usize>| {
1101                    assert_eq!(**data, 10);
1102                    HttpResponse::Ok()
1103                }),
1104            ),
1105        ))
1106        .await;
1107
1108        let req = TestRequest::with_uri("/app/t").to_request();
1109        let resp = call_service(&srv, req).await;
1110        assert_eq!(resp.status(), StatusCode::OK);
1111    }
1112
1113    #[actix_rt::test]
1114    async fn test_scope_config() {
1115        let srv = init_service(App::new().service(web::scope("/app").configure(|s| {
1116            s.route("/path1", web::get().to(HttpResponse::Ok));
1117        })))
1118        .await;
1119
1120        let req = TestRequest::with_uri("/app/path1").to_request();
1121        let resp = srv.call(req).await.unwrap();
1122        assert_eq!(resp.status(), StatusCode::OK);
1123    }
1124
1125    #[actix_rt::test]
1126    async fn test_scope_config_2() {
1127        let srv = init_service(App::new().service(web::scope("/app").configure(|s| {
1128            s.service(web::scope("/v1").configure(|s| {
1129                s.route("/", web::get().to(HttpResponse::Ok));
1130            }));
1131        })))
1132        .await;
1133
1134        let req = TestRequest::with_uri("/app/v1/").to_request();
1135        let resp = srv.call(req).await.unwrap();
1136        assert_eq!(resp.status(), StatusCode::OK);
1137    }
1138
1139    #[actix_rt::test]
1140    async fn test_url_for_external() {
1141        let srv = init_service(App::new().service(web::scope("/app").configure(|s| {
1142            s.service(web::scope("/v1").configure(|s| {
1143                s.external_resource("youtube", "https://youtube.com/watch/{video_id}");
1144                s.route(
1145                    "/",
1146                    web::get().to(|req: HttpRequest| {
1147                        HttpResponse::Ok()
1148                            .body(req.url_for("youtube", ["xxxxxx"]).unwrap().to_string())
1149                    }),
1150                );
1151            }));
1152        })))
1153        .await;
1154
1155        let req = TestRequest::with_uri("/app/v1/").to_request();
1156        let resp = srv.call(req).await.unwrap();
1157        assert_eq!(resp.status(), StatusCode::OK);
1158        let body = read_body(resp).await;
1159        assert_eq!(body, &b"https://youtube.com/watch/xxxxxx"[..]);
1160    }
1161
1162    #[actix_rt::test]
1163    async fn test_url_for_nested() {
1164        let srv = init_service(App::new().service(web::scope("/a").service(
1165            web::scope("/b").service(web::resource("/c/{stuff}").name("c").route(web::get().to(
1166                |req: HttpRequest| {
1167                    HttpResponse::Ok().body(format!("{}", req.url_for("c", ["12345"]).unwrap()))
1168                },
1169            ))),
1170        )))
1171        .await;
1172
1173        let req = TestRequest::with_uri("/a/b/c/test").to_request();
1174        let resp = call_service(&srv, req).await;
1175        assert_eq!(resp.status(), StatusCode::OK);
1176        let body = read_body(resp).await;
1177        assert_eq!(
1178            body,
1179            Bytes::from_static(b"http://localhost:8080/a/b/c/12345")
1180        );
1181    }
1182
1183    #[actix_rt::test]
1184    async fn dynamic_scopes() {
1185        let srv = init_service(
1186            App::new().service(
1187                web::scope("/{a}/").service(
1188                    web::scope("/{b}/")
1189                        .route("", web::get().to(|_: HttpRequest| HttpResponse::Created()))
1190                        .route(
1191                            "/",
1192                            web::get().to(|_: HttpRequest| HttpResponse::Accepted()),
1193                        )
1194                        .route("/{c}", web::get().to(|_: HttpRequest| HttpResponse::Ok())),
1195                ),
1196            ),
1197        )
1198        .await;
1199
1200        // note the unintuitive behavior with trailing slashes on scopes with dynamic segments
1201        let req = TestRequest::with_uri("/a//b//c").to_request();
1202        let resp = call_service(&srv, req).await;
1203        assert_eq!(resp.status(), StatusCode::OK);
1204
1205        let req = TestRequest::with_uri("/a//b/").to_request();
1206        let resp = call_service(&srv, req).await;
1207        assert_eq!(resp.status(), StatusCode::CREATED);
1208
1209        let req = TestRequest::with_uri("/a//b//").to_request();
1210        let resp = call_service(&srv, req).await;
1211        assert_eq!(resp.status(), StatusCode::ACCEPTED);
1212
1213        let req = TestRequest::with_uri("/a//b//c/d").to_request();
1214        let resp = call_service(&srv, req).await;
1215        assert_eq!(resp.status(), StatusCode::NOT_FOUND);
1216
1217        let srv = init_service(
1218            App::new().service(
1219                web::scope("/{a}").service(
1220                    web::scope("/{b}")
1221                        .route("", web::get().to(|_: HttpRequest| HttpResponse::Created()))
1222                        .route(
1223                            "/",
1224                            web::get().to(|_: HttpRequest| HttpResponse::Accepted()),
1225                        )
1226                        .route("/{c}", web::get().to(|_: HttpRequest| HttpResponse::Ok())),
1227                ),
1228            ),
1229        )
1230        .await;
1231
1232        let req = TestRequest::with_uri("/a/b/c").to_request();
1233        let resp = call_service(&srv, req).await;
1234        assert_eq!(resp.status(), StatusCode::OK);
1235
1236        let req = TestRequest::with_uri("/a/b").to_request();
1237        let resp = call_service(&srv, req).await;
1238        assert_eq!(resp.status(), StatusCode::CREATED);
1239
1240        let req = TestRequest::with_uri("/a/b/").to_request();
1241        let resp = call_service(&srv, req).await;
1242        assert_eq!(resp.status(), StatusCode::ACCEPTED);
1243
1244        let req = TestRequest::with_uri("/a/b/c/d").to_request();
1245        let resp = call_service(&srv, req).await;
1246        assert_eq!(resp.status(), StatusCode::NOT_FOUND);
1247    }
1248}