Skip to main content

ntex/web/
resource.rs

1use std::marker::PhantomData;
2
3use crate::error::{Failure, IntoFailure};
4use crate::http::Response;
5use crate::router::{IntoPattern, ResourceDef};
6use crate::service::{Identity, ServiceChainFactory};
7use crate::{Ctx, IntoServiceFactory, Middleware, Service, ServiceFactory, factory};
8
9use super::dev::{WebServiceConfig, WebServiceFactory, insert_slash};
10use super::error::{WebError, WebResponseError};
11use super::guard::Guard;
12use super::route::{IntoRoutes, Route, RouteService};
13use super::stack::{Filter, WebStack};
14use super::{
15    FromRequest, Handler, HandlerSt, HttpHandler, HttpService, State, WebRequest, WebResponse,
16};
17
18/// Groups routes and configuration for one or more URL patterns.
19///
20/// A resource is useful when several routes belong to the same path. Along
21/// with those routes, it can have its own guards, filters, middleware, name,
22/// and fallback service. Create one with [`web::resource()`], then register it
23/// with [`App::service()`] or [`Scope::service()`].
24///
25/// For each request, the router first checks the resource's path and guards.
26/// If they do not match, it keeps looking for another resource or scope. Once
27/// this resource is selected, its middleware and filters run, followed by its
28/// routes in registration order. The first route whose guards all pass handles
29/// the request.
30///
31/// When none of the routes match, the resource uses its own fallback. By
32/// default, that fallback returns `405 Method Not Allowed`.
33///
34/// Use [`Resource::route()`] to add guarded routes, or [`Resource::to()`] to
35/// add an unguarded handler that accepts any request reaching the resource.
36///
37/// ```rust
38/// use ntex::web::{self, App, HttpResponse};
39///
40/// App::default().service(
41///     web::resource("/users")
42///         .route(web::get().to(async || "users"))
43///         .route(web::post().to(async || HttpResponse::Created()))
44///         .default_service(web::to(async || {
45///             HttpResponse::MethodNotAllowed().body("Use GET or POST")
46///         })),
47/// );
48/// ```
49///
50/// [`App::service()`]: super::App::service
51/// [`Scope::service()`]: super::Scope::service
52/// [`web::resource()`]: super::resource
53#[derive(derive_more::Debug)]
54#[debug("Resource({rdef:?})")]
55pub struct Resource<St: State, In, Out = In, M = Identity, F = Filter<St, In>> {
56    middleware: M,
57    filter: ServiceChainFactory<F, St, WebRequest<In>>,
58    rdef: Vec<String>,
59    name: Option<String>,
60    guards: Vec<Box<dyn Guard>>,
61    ph: PhantomData<Out>,
62}
63
64/// A resource with routes or a configured fallback.
65///
66/// From here, you can add more routes or handlers and change the resource
67/// fallback. Configure resource-wide settings such as its name, guards,
68/// filters, and middleware before adding the first route or fallback.
69///
70/// The finished builder can be registered with [`App::service()`] or
71/// [`Scope::service()`]. It keeps the path and all configuration from the
72/// original [`Resource`].
73///
74/// [`App::service()`]: super::App::service
75/// [`Scope::service()`]: super::Scope::service
76#[derive(derive_more::Debug)]
77#[debug("ResourceServices({rdef:?})")]
78pub struct ResourceServices<St: State, In, Out, M, F> {
79    middleware: M,
80    filter: ServiceChainFactory<F, St, WebRequest<In>>,
81    rdef: Vec<String>,
82    name: Option<String>,
83    guards: Vec<Box<dyn Guard>>,
84    routes: Vec<Route<St, Out>>,
85    default: Option<HttpService<St, Out>>,
86}
87
88impl<St: State, In: 'static> Resource<St, In, In> {
89    #[allow(clippy::needless_pass_by_value)]
90    /// Create a resource for the given path pattern or patterns.
91    ///
92    /// Same as [`web::resource()`](crate::web::resource).
93    pub fn new<T: IntoPattern>(path: T) -> Resource<St, In, In> {
94        Resource {
95            rdef: path.patterns(),
96            name: None,
97            middleware: Identity,
98            filter: factory(Filter::new()),
99            guards: Vec::new(),
100            ph: PhantomData,
101        }
102    }
103}
104
105impl<St, In, Out, M, F> Resource<St, In, Out, M, F>
106where
107    St: State,
108    In: 'static,
109    Out: 'static,
110    F: ServiceFactory<
111            St,
112            WebRequest<In>,
113            Res = WebRequest<Out>,
114            Error = WebError<St, St::Error>,
115            InitError = Failure,
116        >,
117{
118    /// Set resource name.
119    ///
120    /// Name is used for url generation.
121    #[must_use]
122    pub fn name(mut self, name: &str) -> Self {
123        self.name = Some(name.to_string());
124        self
125    }
126
127    /// Add a match guard to this resource.
128    ///
129    /// The resource is selected only when its path and all registered guards
130    /// match. If a guard rejects the request, the router can try another
131    /// resource with the same path; otherwise the containing scope or
132    /// application fallback is used.
133    ///
134    /// Resource guards run before route selection. Use [`Route::guard()`] when
135    /// the condition should choose between routes inside one resource.
136    ///
137    /// ```rust
138    /// use ntex::web::{self, guard, App};
139    ///
140    /// App::default()
141    ///     .service(
142    ///         web::resource("/items")
143    ///             .guard(guard::Header("accept", "application/json"))
144    ///             .to(async || "JSON items")
145    ///     )
146    ///     .service(
147    ///         web::resource("/items")
148    ///             .guard(guard::Header("accept", "text/plain"))
149    ///             .to(async || "Text items")
150    ///     );
151    /// ```
152    #[must_use]
153    pub fn guard<G: Guard + 'static>(mut self, guard: G) -> Self {
154        self.guards.push(Box::new(guard));
155        self
156    }
157
158    pub(crate) fn add_guards(mut self, guards: Vec<Box<dyn Guard>>) -> Self {
159        self.guards.extend(guards);
160        self
161    }
162
163    /// Registers a request filter for this resource.
164    ///
165    /// The filter runs after the resource's path and guards match, but before
166    /// its routes are checked. It therefore also runs when no route matches
167    /// and the resource's default service is used.
168    ///
169    /// ```rust
170    /// use std::convert::Infallible;
171    /// use ntex::web::{self, App, WebRequest};
172    ///
173    /// async fn item(_state: &(), item_id: usize) -> String {
174    ///     format!("Item {item_id}")
175    /// }
176    ///
177    /// App::new().service(
178    ///     web::resource("/item")
179    ///         .filter(async |req: WebRequest<()>| {
180    ///             Ok::<_, Infallible>(req.map_state(|()| 10usize))
181    ///         })
182    ///         .route(web::get().to_with_state(item)),
183    /// );
184    /// ```
185    #[must_use]
186    pub fn filter<U, R>(
187        self,
188        filter: impl IntoServiceFactory<U, St, WebRequest<Out>>,
189    ) -> Resource<
190        St,
191        In,
192        R,
193        M,
194        impl ServiceFactory<
195            St,
196            WebRequest<In>,
197            Res = WebRequest<R>,
198            Error = WebError<St, St::Error>,
199            InitError = Failure,
200        >,
201    >
202    where
203        U: ServiceFactory<St, WebRequest<Out>, Res = WebRequest<R>>,
204        U::Error: WebResponseError<St, St::Error>,
205        U::InitError: IntoFailure,
206    {
207        Resource {
208            filter: self.filter.and_then(
209                filter
210                    .into_factory()
211                    .map_err(WebError::from_err)
212                    .map_init_err(IntoFailure::fail),
213            ),
214            middleware: self.middleware,
215            rdef: self.rdef,
216            name: self.name,
217            guards: self.guards,
218            ph: PhantomData,
219        }
220    }
221
222    /// Registers a middleware for this resource.
223    ///
224    /// The middleware runs only after the resource's path and guards match. It
225    /// wraps the resource filter, routes, and fallback service, so it can
226    /// inspect or modify both the request and response. It also runs when no
227    /// route matches and the resource fallback handles the request.
228    ///
229    /// ```rust
230    /// use ntex::web::{self, middleware, App};
231    ///
232    /// App::default().service(
233    ///     web::resource("/items")
234    ///         .middleware(middleware::Logger::default())
235    ///         .route(web::get().to(async || "Items")),
236    /// );
237    /// ```
238    #[must_use]
239    pub fn middleware<U>(self, mw: U) -> Resource<St, In, Out, WebStack<St, U, M>, F> {
240        Resource {
241            middleware: WebStack::new(mw, self.middleware),
242            filter: self.filter,
243            rdef: self.rdef,
244            name: self.name,
245            guards: self.guards,
246            ph: PhantomData,
247        }
248    }
249
250    /// Add one or more routes to this resource.
251    ///
252    /// Routes are checked in registration order after the resource path and
253    /// resource guards match. The first route whose method and custom guards
254    /// accept the request is called. If no route matches, the resource's
255    /// default service is used; without a custom default, it returns
256    /// `405 Method Not Allowed`.
257    ///
258    /// A single [`Route`] or a collection of routes can be supplied.
259    ///
260    /// ```rust
261    /// use ntex::web::{self, App, HttpResponse};
262    ///
263    /// App::default().service(
264    ///     web::resource("/items").route([
265    ///         web::get().to(async || "list"),
266    ///         web::post().to(async || HttpResponse::Created()),
267    ///         web::delete().to(async || HttpResponse::NoContent()),
268    ///     ])
269    /// );
270    /// ```
271    #[must_use]
272    pub fn route<R>(self, route: R) -> ResourceServices<St, In, Out, M, F>
273    where
274        R: IntoRoutes<St, Out>,
275    {
276        let mut routes = Vec::new();
277        for route in route.routes() {
278            routes.push(route);
279        }
280
281        ResourceServices {
282            routes,
283            name: self.name,
284            rdef: self.rdef,
285            guards: self.guards,
286            filter: self.filter,
287            middleware: self.middleware,
288            default: None,
289        }
290    }
291
292    /// Register route with a handler.
293    ///
294    /// The route matches every request after this resource's path and guards
295    /// match. The handler receives request extractor values and returns a type
296    /// implementing [`Responder`](super::Responder).
297    ///
298    /// ```rust
299    /// use ntex::web::{self, App};
300    ///
301    /// async fn show_user(id: web::types::Path<u32>) -> String {
302    ///     format!("User {}", id.into_inner())
303    /// }
304    ///
305    /// App::default()
306    ///     .service(web::resource("/users/{id}").to(show_user));
307    /// ```
308    ///
309    /// This is equivalent to `resource.route(web::route().to(handler))`.
310    #[must_use]
311    pub fn to<Args>(self, h: impl Handler<St, Args>) -> ResourceServices<St, In, Out, M, F>
312    where
313        Args: FromRequest<St> + 'static,
314        Args::Error: WebResponseError<St, St::Error>,
315    {
316        ResourceServices {
317            name: self.name,
318            rdef: self.rdef,
319            guards: self.guards,
320            filter: self.filter,
321            middleware: self.middleware,
322            default: None,
323            routes: vec![Route::new().to(h)],
324        }
325    }
326
327    /// Register a state-aware handler as a new route.
328    ///
329    /// The handler receives a shared reference to the application state,
330    /// followed by the current request state and any request extractors.
331    ///
332    /// ```rust
333    /// use ntex::web;
334    ///
335    /// struct AppState {
336    ///     greeting: &'static str,
337    /// }
338    ///
339    /// impl web::State for AppState {
340    ///     type Error = web::DefaultError;
341    /// }
342    ///
343    /// async fn index(
344    ///     state: &AppState,
345    ///     request_state: (),
346    ///     name: web::types::Path<String>,
347    /// ) -> String {
348    ///     let _ = request_state;
349    ///     format!("{}, {}!", state.greeting, name.into_inner())
350    /// }
351    ///
352    /// web::App::<AppState>::new()
353    ///     .service(web::resource("/{name}").to_with_state(index));
354    /// ```
355    ///
356    /// This is equivalent to `resource.route(web::route().to_with_state(handler))`.
357    #[must_use]
358    pub fn to_with_state<Args>(
359        self,
360        h: impl HandlerSt<St, Out, Args>,
361    ) -> ResourceServices<St, In, Out, M, F>
362    where
363        Args: FromRequest<St> + 'static,
364        Args::Error: WebResponseError<St, St::Error>,
365    {
366        ResourceServices {
367            name: self.name,
368            rdef: self.rdef,
369            guards: self.guards,
370            filter: self.filter,
371            middleware: self.middleware,
372            default: None,
373            routes: vec![Route::new().to_with_state(h)],
374        }
375    }
376
377    /// Set the fallback service for this resource.
378    ///
379    /// The fallback is called after the resource path and guards match but none
380    /// of its routes match, commonly because the request method is unsupported.
381    /// Without a custom fallback, the resource returns
382    /// `405 Method Not Allowed`. It does not delegate to a scope or application
383    /// fallback.
384    ///
385    /// ```rust
386    /// use ntex::web::{self, App, HttpResponse};
387    ///
388    /// App::default().service(
389    ///     web::resource("/items")
390    ///         .route(web::get().to(async || "items"))
391    ///         .default_service(web::to(async || {
392    ///             HttpResponse::MethodNotAllowed().body("Use GET")
393    ///         }))
394    /// );
395    /// ```
396    #[must_use]
397    pub fn default_service<S>(
398        self,
399        f: impl IntoServiceFactory<S, St, WebRequest<Out>>,
400    ) -> ResourceServices<St, In, Out, M, F>
401    where
402        S: ServiceFactory<St, WebRequest<Out>, Res = WebResponse> + 'static,
403        S::Error: WebResponseError<St, St::Error>,
404        S::InitError: IntoFailure,
405    {
406        // create and configure default resource
407        ResourceServices {
408            name: self.name,
409            rdef: self.rdef,
410            guards: self.guards,
411            filter: self.filter,
412            routes: Vec::new(),
413            middleware: self.middleware,
414            default: Some(HttpService::new(
415                f.into_factory()
416                    .map_err(WebError::from_err)
417                    .map_init_err(IntoFailure::fail),
418            )),
419        }
420    }
421}
422
423impl<St, In, Out, M, F> ResourceServices<St, In, Out, M, F>
424where
425    St: State,
426    In: 'static,
427    Out: 'static,
428    M: 'static,
429    F: ServiceFactory<
430            St,
431            WebRequest<In>,
432            Res = WebRequest<Out>,
433            Error = WebError<St, St::Error>,
434            InitError = Failure,
435        >,
436{
437    /// Add one or more routes to this resource.
438    ///
439    /// Routes are checked in registration order after the resource path and
440    /// resource guards match. The first route whose method and custom guards
441    /// accept the request is called. If no route matches, the resource's
442    /// default service is used; without a custom default, it returns
443    /// `405 Method Not Allowed`.
444    ///
445    /// A single [`Route`] or a collection of routes can be supplied.
446    ///
447    /// ```rust
448    /// use ntex::web::{self, App, HttpResponse};
449    ///
450    /// App::default().service(
451    ///     web::resource("/items")
452    ///         .route(web::get().to(async || "list"))
453    ///         .route([
454    ///             web::post().to(async || HttpResponse::Created()),
455    ///             web::delete().to(async || HttpResponse::NoContent()),
456    ///         ])
457    /// );
458    /// ```
459    #[must_use]
460    pub fn route<R>(mut self, route: R) -> Self
461    where
462        R: IntoRoutes<St, Out>,
463    {
464        for route in route.routes() {
465            self.routes.push(route);
466        }
467        self
468    }
469
470    /// Add an unguarded route with a handler.
471    ///
472    /// The route matches every request not accepted by an earlier route.
473    /// Routes are checked in registration order, so this catch-all route should
474    /// normally be added last; routes added after it cannot be selected.
475    ///
476    /// The handler receives request extractor values and returns a type
477    /// implementing [`Responder`](super::Responder).
478    ///
479    /// ```rust
480    /// use ntex::web::{self, App};
481    ///
482    /// App::default().service(
483    ///     web::resource("/items")
484    ///         .route(web::get().to(async || "list"))
485    ///         .to(async || "Unsupported request")
486    /// );
487    /// ```
488    #[must_use]
489    pub fn to<Args>(mut self, handler: impl Handler<St, Args>) -> Self
490    where
491        Args: FromRequest<St> + 'static,
492        Args::Error: WebResponseError<St, St::Error>,
493    {
494        self.routes.push(Route::new().to(handler));
495        self
496    }
497
498    /// Add a state-aware handler as a new route.
499    ///
500    /// The route has no guards and matches any request not accepted by an
501    /// earlier route. The handler receives a shared reference to the
502    /// application state, followed by the current request state and any request
503    /// extractors.
504    ///
505    /// ```rust
506    /// use ntex::web;
507    ///
508    /// struct AppState;
509    ///
510    /// impl web::State for AppState {
511    ///     type Error = web::DefaultError;
512    /// }
513    ///
514    /// async fn fallback(
515    ///     _state: &AppState,
516    ///     _request_state: (),
517    ///     req: web::HttpRequest,
518    /// ) -> String {
519    ///     format!("No route for {}", req.path())
520    /// }
521    ///
522    /// web::App::<AppState>::new().service(
523    ///     web::resource("/")
524    ///         .route(web::get().to(async || "GET"))
525    ///         .to_with_state(fallback)
526    /// );
527    /// ```
528    #[must_use]
529    pub fn to_with_state<Args>(mut self, handler: impl HandlerSt<St, Out, Args>) -> Self
530    where
531        Args: FromRequest<St> + 'static,
532        Args::Error: WebResponseError<St, St::Error>,
533    {
534        self.routes.push(Route::new().to_with_state(handler));
535        self
536    }
537
538    /// Set the fallback service for this resource.
539    ///
540    /// The fallback is called after the resource path and guards match but none
541    /// of its routes match, commonly because the request method is unsupported.
542    /// Without a custom fallback, the resource returns
543    /// `405 Method Not Allowed`. It does not delegate to a scope or application
544    /// fallback.
545    ///
546    /// ```rust
547    /// use ntex::web::{self, App, HttpResponse};
548    ///
549    /// App::default().service(
550    ///     web::resource("/items")
551    ///         .route(web::get().to(async || "items"))
552    ///         .default_service(web::to(async || {
553    ///             HttpResponse::MethodNotAllowed().body("Use GET")
554    ///         }))
555    /// );
556    /// ```
557    #[must_use]
558    pub fn default_service<S>(mut self, f: impl IntoServiceFactory<S, St, WebRequest<Out>>) -> Self
559    where
560        S: ServiceFactory<St, WebRequest<Out>, Res = WebResponse> + 'static,
561        S::Error: WebResponseError<St, St::Error>,
562        S::InitError: IntoFailure,
563    {
564        // create and configure default resource
565        self.default = Some(HttpService::new(
566            f.into_factory()
567                .map_err(WebError::from_err)
568                .map_init_err(IntoFailure::fail),
569        ));
570
571        self
572    }
573}
574
575impl<St, Outer, In, Out, M, F> WebServiceFactory<St, Outer> for ResourceServices<St, In, Out, M, F>
576where
577    St: State,
578    Outer: 'static,
579    In: 'static,
580    Out: 'static,
581    F: ServiceFactory<
582            St,
583            WebRequest<In>,
584            Res = WebRequest<Out>,
585            Error = WebError<St, St::Error>,
586            InitError = Failure,
587        > + 'static,
588    M: Middleware<ResourceService<St, In, Out, F::Service>, St> + 'static,
589    M::Service: Service<St, WebRequest<Outer>, Res = WebResponse, Error = WebError<St, St::Error>>,
590{
591    fn register(mut self, config: &mut WebServiceConfig<St, Outer>) {
592        let guards = if self.guards.is_empty() {
593            None
594        } else {
595            Some(std::mem::take(&mut self.guards))
596        };
597        let mut rdef = if config.is_root() || !self.rdef.is_empty() {
598            ResourceDef::new(insert_slash(self.rdef.clone()))
599        } else {
600            ResourceDef::new(self.rdef.clone())
601        };
602        if let Some(ref name) = self.name {
603            rdef.set_name(name.clone());
604        }
605
606        config.register_service(
607            rdef,
608            guards,
609            None,
610            ResourceServiceFactory {
611                middleware: self.middleware,
612                filter: self.filter,
613                routes: self.routes,
614                default: self.default.take(),
615                ph: PhantomData,
616            },
617        );
618    }
619}
620
621impl<St, Outer, In, Out, M, F>
622    IntoServiceFactory<
623        ResourceServiceFactory<St, In, Out, M, ServiceChainFactory<F, St, WebRequest<In>>>,
624        St,
625        WebRequest<Outer>,
626    > for ResourceServices<St, In, Out, M, F>
627where
628    St: State,
629    Outer: 'static,
630    In: 'static,
631    Out: 'static,
632    F: ServiceFactory<
633            St,
634            WebRequest<In>,
635            Res = WebRequest<Out>,
636            Error = WebError<St, St::Error>,
637            InitError = Failure,
638        > + 'static,
639    M: Middleware<ResourceService<St, In, Out, F::Service>, St> + 'static,
640    M::Service: Service<St, WebRequest<Outer>, Res = WebResponse, Error = WebError<St, St::Error>>,
641{
642    fn into_factory(
643        mut self,
644    ) -> ResourceServiceFactory<St, In, Out, M, ServiceChainFactory<F, St, WebRequest<In>>> {
645        ResourceServiceFactory {
646            middleware: self.middleware,
647            filter: self.filter,
648            routes: self.routes,
649            default: self.default.take(),
650            ph: PhantomData,
651        }
652    }
653}
654
655/// Resource service factory
656#[derive(derive_more::Debug)]
657#[debug("ResourceServiceFactory")]
658pub struct ResourceServiceFactory<St: State, In, Out, M, F> {
659    middleware: M,
660    filter: F,
661    routes: Vec<Route<St, Out>>,
662    default: Option<HttpService<St, Out>>,
663    ph: PhantomData<In>,
664}
665
666impl<St, Outer, In, Out, M, F> ServiceFactory<St, WebRequest<Outer>>
667    for ResourceServiceFactory<St, In, Out, M, F>
668where
669    St: State,
670    Out: 'static,
671    F: ServiceFactory<
672            St,
673            WebRequest<In>,
674            Res = WebRequest<Out>,
675            Error = WebError<St, St::Error>,
676            InitError = Failure,
677        > + 'static,
678    M: Middleware<ResourceService<St, In, Out, F::Service>, St> + 'static,
679    M::Service: Service<St, WebRequest<Outer>, Res = WebResponse, Error = WebError<St, St::Error>>,
680{
681    type Res = WebResponse;
682    type Error = WebError<St, St::Error>;
683
684    type Service = M::Service;
685    type InitError = Failure;
686
687    async fn create(&self, st: &St) -> Result<Self::Service, Self::InitError> {
688        let filter = self.filter.create(st).await?;
689        let default = if let Some(ref default) = self.default {
690            Some(default.create(st).await?)
691        } else {
692            None
693        };
694
695        Ok(self.middleware.create(
696            st,
697            ResourceService {
698                filter,
699                default,
700                routes: self.routes.iter().map(Route::service).collect(),
701                ph: PhantomData,
702            },
703        ))
704    }
705}
706
707/// Resource service
708#[derive(derive_more::Debug)]
709#[debug("ResourceService")]
710pub struct ResourceService<St: State, In, Out, F> {
711    filter: F,
712    routes: Vec<RouteService<St, Out>>,
713    default: Option<HttpHandler<St, Out>>,
714    ph: PhantomData<In>,
715}
716
717impl<St, In, Out, F> Service<St, WebRequest<In>> for ResourceService<St, In, Out, F>
718where
719    St: State,
720    F: Service<St, WebRequest<In>, Res = WebRequest<Out>, Error = WebError<St, St::Error>>,
721{
722    type Res = WebResponse;
723    type Error = WebError<St, St::Error>;
724
725    async fn call(
726        &self,
727        req: WebRequest<In>,
728        ctx: Ctx<'_, Self, St>,
729    ) -> Result<Self::Res, Self::Error> {
730        let mut req = ctx.call(&self.filter, req).await?;
731
732        for route in &self.routes {
733            if route.check(&mut req) {
734                return ctx.call(route, req).await;
735            }
736        }
737        if let Some(ref default) = self.default {
738            ctx.call(default, req).await
739        } else {
740            Ok(WebResponse::new(
741                Response::MethodNotAllowed().build(),
742                req.into_parts().0,
743            ))
744        }
745    }
746}
747
748#[cfg(test)]
749mod tests {
750    use std::convert::Infallible;
751
752    use crate::http::{Method, StatusCode};
753    use crate::time::{Millis, sleep};
754    use crate::web::test::{TestRequest, call_service, init_service};
755    use crate::web::{self, App, HttpResponse, guard, request::WebRequest};
756
757    #[crate::rt_test]
758    async fn test_filter() {
759        let filter = std::rc::Rc::new(std::cell::Cell::new(false));
760        let filter2 = filter.clone();
761        let srv = init_service(
762            App::new().service(
763                web::resource("/test")
764                    .filter(async move |req: WebRequest<()>| {
765                        filter2.set(true);
766                        Ok::<_, Infallible>(req)
767                    })
768                    .route(web::get().to(async || HttpResponse::Ok())),
769            ),
770        )
771        .await;
772        let req = TestRequest::with_uri("/test").to_request();
773        let resp = call_service(&srv, req).await;
774        assert_eq!(resp.status(), StatusCode::OK);
775        assert!(filter.get());
776    }
777
778    #[crate::rt_test]
779    async fn test_to() {
780        let srv = init_service(App::new().service(web::resource("/test").to(async || {
781            sleep(Millis(100)).await;
782            HttpResponse::Ok()
783        })))
784        .await;
785        let req = TestRequest::with_uri("/test").to_request();
786        let resp = call_service(&srv, req).await;
787        assert_eq!(resp.status(), StatusCode::OK);
788    }
789
790    #[crate::rt_test]
791    async fn test_pattern() {
792        let srv = init_service(
793            App::new().service(web::resource(["/test", "/test2"]).to(async || HttpResponse::Ok())),
794        )
795        .await;
796        let req = TestRequest::with_uri("/test").to_request();
797        let resp = call_service(&srv, req).await;
798        assert_eq!(resp.status(), StatusCode::OK);
799        let req = TestRequest::with_uri("/test2").to_request();
800        let resp = call_service(&srv, req).await;
801        assert_eq!(resp.status(), StatusCode::OK);
802    }
803
804    #[crate::rt_test]
805    async fn test_default_resource() {
806        let srv = init_service(
807            App::new()
808                .service(web::resource("/test").route(web::get().to(async || HttpResponse::Ok())))
809                .default_service(async move |r: WebRequest<()>| {
810                    Ok::<_, Infallible>(r.into_response(HttpResponse::BadRequest()))
811                }),
812        )
813        .await;
814        let req = TestRequest::with_uri("/test").to_request();
815        let resp = call_service(&srv, req).await;
816        assert_eq!(resp.status(), StatusCode::OK);
817
818        let req = TestRequest::with_uri("/test")
819            .method(Method::POST)
820            .to_request();
821        let resp = call_service(&srv, req).await;
822        assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED);
823
824        let srv = init_service(
825            App::new().service(
826                web::resource("/test")
827                    .route(web::get().to(async || HttpResponse::Ok()))
828                    .default_service(async move |r: WebRequest<()>| {
829                        Ok::<_, Infallible>(r.into_response(HttpResponse::BadRequest()))
830                    }),
831            ),
832        )
833        .await;
834
835        let req = TestRequest::with_uri("/test").to_request();
836        let resp = call_service(&srv, req).await;
837        assert_eq!(resp.status(), StatusCode::OK);
838
839        let req = TestRequest::with_uri("/test")
840            .method(Method::POST)
841            .to_request();
842        let resp = call_service(&srv, req).await;
843        assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
844    }
845
846    #[crate::rt_test]
847    async fn test_resource_guards() {
848        let srv = init_service(
849            App::new()
850                .service(
851                    web::resource("/test/{p}")
852                        .guard(guard::Get())
853                        .to(async || HttpResponse::Ok()),
854                )
855                .service(
856                    web::resource("/test/{p}")
857                        .guard(guard::Put())
858                        .to(async || HttpResponse::Created()),
859                )
860                .service(
861                    web::resource("/test/{p}")
862                        .guard(guard::Delete())
863                        .to(async || HttpResponse::NoContent()),
864                ),
865        )
866        .await;
867
868        let req = TestRequest::with_uri("/test/it")
869            .method(Method::GET)
870            .to_request();
871        let resp = call_service(&srv, req).await;
872        assert_eq!(resp.status(), StatusCode::OK);
873
874        let req = TestRequest::with_uri("/test/it")
875            .method(Method::PUT)
876            .to_request();
877        let resp = call_service(&srv, req).await;
878        assert_eq!(resp.status(), StatusCode::CREATED);
879
880        let req = TestRequest::with_uri("/test/it")
881            .method(Method::DELETE)
882            .to_request();
883        let resp = call_service(&srv, req).await;
884        assert_eq!(resp.status(), StatusCode::NO_CONTENT);
885    }
886
887    #[crate::rt_test]
888    async fn test_middleware() {
889        use crate::http::header::{CONTENT_TYPE, HeaderValue};
890        use crate::web::middleware::DefaultHeaders;
891
892        let srv = init_service(
893            App::new().service(
894                web::resource("/test")
895                    .middleware(DefaultHeaders::new().header(CONTENT_TYPE, "text/plain"))
896                    .route(web::get().to(async || HttpResponse::Ok())),
897            ),
898        )
899        .await;
900        for method in [Method::GET, Method::POST] {
901            let req = TestRequest::with_uri("/test").method(method).to_request();
902            let resp = call_service(&srv, req).await;
903            assert_eq!(
904                resp.headers().get(CONTENT_TYPE),
905                Some(&HeaderValue::from_static("text/plain"))
906            );
907        }
908    }
909
910    #[crate::rt_test]
911    async fn test_empty_path_in_scope() {
912        let srv = init_service(App::new().service(
913            web::scope("/app").service(web::resource("").to(async || HttpResponse::Ok())),
914        ))
915        .await;
916        let resp = call_service(&srv, TestRequest::with_uri("/app").to_request()).await;
917        assert_eq!(resp.status(), StatusCode::OK);
918        let resp = call_service(&srv, TestRequest::with_uri("/app/").to_request()).await;
919        assert_eq!(resp.status(), StatusCode::NOT_FOUND);
920    }
921
922    #[crate::rt_test]
923    async fn test_into_factory() {
924        use crate::service::{IntoServiceFactory, ServiceFactory};
925
926        let res = web::resource("/test")
927            .route(web::get().to(async || HttpResponse::Ok()))
928            .default_service(async |r: WebRequest<()>| {
929                Ok::<_, Infallible>(r.into_response(HttpResponse::MethodNotAllowed()))
930            });
931        let srv = IntoServiceFactory::<_, (), WebRequest<()>>::into_factory(res)
932            .pipeline(())
933            .await
934            .unwrap();
935
936        let resp = srv
937            .call(TestRequest::default().to_srv_request())
938            .await
939            .unwrap();
940        assert_eq!(resp.status(), StatusCode::OK);
941        let req = TestRequest::default().method(Method::PUT).to_srv_request();
942        let resp = srv.call(req).await.unwrap();
943        assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED);
944    }
945}