Skip to main content

ntex/web/
service.rs

1use std::{marker::PhantomData, rc::Rc};
2
3use crate::error::{Failure, IntoFailure};
4use crate::router::{IntoPattern, ResourceDef};
5use crate::service::{IntoServiceFactory, ServiceFactory, boxed};
6
7use super::error::{WebError, WebResponseError};
8use super::guard::{AllGuard, Guard};
9use super::{HttpService, State, WebRequest, WebResponse, dev::insert_slash, rmap::ResourceMap};
10
11/// Factory of web services that can be registered with an application or scope.
12///
13/// `App::service()` and `Scope::service()` accept any `WebServiceFactory`.
14/// It is implemented by [`Resource`](crate::web::Resource),
15/// [`Scope`](crate::web::Scope), services generated by the route attribute
16/// macros, and services built with `web::service()`.
17pub trait WebServiceFactory<St: State, In>: 'static {
18    /// Register the service, its path, and its guards in `config`.
19    fn register(self, config: &mut WebServiceConfig<St, In>);
20}
21
22pub(super) trait AppServiceFactory<St: State, In> {
23    fn register(&mut self, config: &mut WebServiceConfig<St, In>);
24}
25
26pub(super) struct ServiceFactoryWrapper<T> {
27    factory: Option<T>,
28}
29
30impl<T> ServiceFactoryWrapper<T> {
31    pub(super) fn new(factory: T) -> Self {
32        Self {
33            factory: Some(factory),
34        }
35    }
36}
37
38impl<T, St, In> AppServiceFactory<St, In> for ServiceFactoryWrapper<T>
39where
40    T: WebServiceFactory<St, In>,
41    St: State,
42{
43    fn register(&mut self, config: &mut WebServiceConfig<St, In>) {
44        if let Some(item) = self.factory.take() {
45            item.register(config);
46        }
47    }
48}
49
50type Guards = Vec<Box<dyn Guard>>;
51
52/// Application service configuration
53#[derive(derive_more::Debug)]
54#[debug("WebServiceConfig")]
55pub struct WebServiceConfig<St: State, In = ()> {
56    root: bool,
57    services: Vec<(
58        ResourceDef,
59        HttpService<St, In>,
60        Option<Guards>,
61        Option<Rc<ResourceMap>>,
62    )>,
63}
64
65impl<St: State, In: 'static> WebServiceConfig<St, In> {
66    /// Create server settings instance
67    pub(crate) fn new() -> Self {
68        WebServiceConfig {
69            root: true,
70            services: Vec::new(),
71        }
72    }
73
74    /// Check if root is being configured
75    pub fn is_root(&self) -> bool {
76        self.root
77    }
78
79    pub(crate) fn into_services(
80        self,
81    ) -> Vec<(
82        ResourceDef,
83        HttpService<St, In>,
84        Option<Guards>,
85        Option<Rc<ResourceMap>>,
86    )> {
87        self.services
88    }
89
90    /// Register http service
91    pub fn register_service<S>(
92        &mut self,
93        rdef: ResourceDef,
94        guards: Option<Vec<Box<dyn Guard>>>,
95        nested: Option<Rc<ResourceMap>>,
96        factory: impl IntoServiceFactory<S, St, WebRequest<In>>,
97    ) where
98        S: ServiceFactory<
99                St,
100                WebRequest<In>,
101                Res = WebResponse,
102                Error = WebError<St, St::Error>,
103                InitError = Failure,
104            > + 'static,
105    {
106        self.services
107            .push((rdef, boxed::factory(factory.into_factory()), guards, nested));
108    }
109}
110
111/// Create service adapter for a specific path.
112///
113/// ```rust
114/// use std::convert::Infallible;
115/// use ntex::web::{self, guard, App, HttpResponse};
116///
117/// async fn my_service(req: web::WebRequest<()>) -> Result<web::WebResponse, Infallible> {
118///     Ok(req.into_response(HttpResponse::Ok().build()))
119/// }
120///
121/// let app = App::default().service(
122///     web::service("/users/*")
123///         .guard(guard::Header("content-type", "text/plain"))
124///         .build(my_service)
125/// );
126/// ```
127#[derive(Debug)]
128pub struct WebServiceAdapter {
129    rdef: Vec<String>,
130    name: Option<String>,
131    guards: AllGuard,
132}
133
134impl WebServiceAdapter {
135    #[must_use]
136    #[allow(clippy::needless_pass_by_value)]
137    /// Create new `WebServiceAdapter` instance.
138    pub fn new<T: IntoPattern>(path: T) -> Self {
139        WebServiceAdapter {
140            rdef: path.patterns(),
141            name: None,
142            guards: AllGuard::default(),
143        }
144    }
145
146    /// Set service name.
147    ///
148    /// Name is used for url generation.
149    #[must_use]
150    pub fn name(mut self, name: &str) -> Self {
151        self.name = Some(name.to_string());
152        self
153    }
154
155    /// Add a match guard to this web service.
156    ///
157    /// The service is selected only when its path and all registered guards
158    /// match. If a guard rejects the request, the router can try another
159    /// matching service; otherwise the containing scope or application fallback
160    /// is used.
161    ///
162    /// ```rust
163    /// use std::convert::Infallible;
164    /// use ntex::web::{self, guard, App, HttpResponse};
165    ///
166    /// async fn index(
167    ///     req: web::WebRequest<()>,
168    /// ) -> Result<web::WebResponse, Infallible> {
169    ///     Ok(req.into_response(HttpResponse::Ok().build()))
170    /// }
171    ///
172    /// App::default().service(
173    ///     web::service("/health")
174    ///         .guard(guard::Get())
175    ///         .guard(guard::Header("x-health-check", "true"))
176    ///         .build(index)
177    /// );
178    /// ```
179    #[must_use]
180    pub fn guard<G: Guard + 'static>(mut self, guard: G) -> Self {
181        self.guards.add(guard);
182        self
183    }
184
185    /// Set a service factory implementation and generate web service.
186    pub fn build<Sf, St, In, F>(self, service: F) -> impl WebServiceFactory<St, In>
187    where
188        St: State,
189        In: 'static,
190        F: IntoServiceFactory<Sf, St, WebRequest<In>>,
191        Sf: ServiceFactory<St, WebRequest<In>, Res = WebResponse> + 'static,
192        Sf::Error: WebResponseError<St, St::Error>,
193        Sf::InitError: IntoFailure,
194    {
195        WebServiceImpl {
196            srv: service
197                .into_factory()
198                .map_err(WebError::from_err)
199                .map_init_err(IntoFailure::fail),
200            rdef: self.rdef,
201            name: self.name,
202            guards: self.guards,
203            ph: PhantomData,
204        }
205    }
206}
207
208struct WebServiceImpl<Sf, In> {
209    srv: Sf,
210    rdef: Vec<String>,
211    name: Option<String>,
212    guards: AllGuard,
213    ph: PhantomData<In>,
214}
215
216impl<Sf, In, St> WebServiceFactory<St, In> for WebServiceImpl<Sf, In>
217where
218    St: State,
219    In: 'static,
220    Sf: ServiceFactory<
221            St,
222            WebRequest<In>,
223            Res = WebResponse,
224            Error = WebError<St, St::Error>,
225            InitError = Failure,
226        > + 'static,
227{
228    fn register(mut self, config: &mut WebServiceConfig<St, In>) {
229        let guards = if self.guards.0.is_empty() {
230            None
231        } else {
232            Some(std::mem::take(&mut self.guards.0))
233        };
234
235        let mut rdef = if config.is_root() || !self.rdef.is_empty() {
236            ResourceDef::new(insert_slash(self.rdef))
237        } else {
238            ResourceDef::new(self.rdef)
239        };
240        if let Some(name) = self.name {
241            rdef.set_name(name);
242        }
243        config.register_service(rdef, guards, None, self.srv);
244    }
245}
246
247#[allow(unused_parens)]
248impl<T, St, In> WebServiceFactory<St, In> for Vec<T>
249where
250    T: WebServiceFactory<St, In> + 'static,
251    St: State,
252{
253    fn register(mut self, config: &mut WebServiceConfig<St, In>) {
254        for service in self.drain(..) {
255            service.register(config);
256        }
257    }
258}
259
260macro_rules! tuple_web_service(
261    {$(#[$meta:meta])* $(($n:tt, $T:ident)),+} => {
262
263        $(#[$meta])*
264        impl<St: State, In, $($T: WebServiceFactory<St, In> + 'static),+> WebServiceFactory<St, In> for ($($T,)+) {
265            fn register(self, config: &mut WebServiceConfig<St, In>) {
266                $(
267                    self.$n.register(config);
268                )+
269            }
270        }
271    }
272);
273
274impl<St, In, T, const N: usize> WebServiceFactory<St, In> for [T; N]
275where
276    St: State,
277    T: WebServiceFactory<St, In> + 'static,
278{
279    fn register(self, config: &mut WebServiceConfig<St, In>) {
280        for t in self {
281            t.register(config);
282        }
283    }
284}
285
286#[allow(non_snake_case, clippy::wildcard_imports)]
287#[rustfmt::skip]
288mod m {
289    use super::*;
290    use variadics_please::all_tuples_enumerated;
291
292    all_tuples_enumerated!(#[doc(fake_variadic)] tuple_web_service, 1, 24, T);
293}
294
295#[cfg(test)]
296mod tests {
297    use std::convert::Infallible;
298
299    use super::*;
300    use crate::http::{Method, StatusCode};
301    use crate::web::test::{TestRequest, init_service};
302    use crate::web::{self, App, DefaultError, HttpResponse, guard};
303
304    #[crate::rt_test]
305    async fn test_service() {
306        let srv = init_service(App::new().service(web::service("/test").name("test").build(
307            async move |req: WebRequest<()>| {
308                Ok::<_, Infallible>(req.into_response(HttpResponse::Ok().build()))
309            },
310        )))
311        .await;
312        let req = TestRequest::with_uri("/test").to_request();
313        let resp = srv.call(req).await.unwrap();
314        assert_eq!(resp.status(), StatusCode::OK);
315
316        let srv = init_service(
317            App::new().service(web::service("/test").guard(guard::Get()).build(
318                async move |req: WebRequest<()>| {
319                    Ok::<_, DefaultError>(req.into_response(HttpResponse::Ok().build()))
320                },
321            )),
322        )
323        .await;
324        let req = TestRequest::with_uri("/test")
325            .method(Method::PUT)
326            .to_request();
327        let resp = srv.call(req).await.unwrap();
328        assert_eq!(resp.status(), StatusCode::NOT_FOUND);
329    }
330
331    #[crate::rt_test]
332    async fn test_multi() {
333        let srv = init_service(App::new().service([
334            web::resource("/test1").to(async || HttpResponse::Ok()),
335            web::resource("/test2").to(async || HttpResponse::Ok()),
336        ]))
337        .await;
338        let req = TestRequest::with_uri("/test1").to_request();
339        let resp = srv.call(req).await.unwrap();
340        assert_eq!(resp.status(), StatusCode::OK);
341        let req = TestRequest::with_uri("/test2").to_request();
342        let resp = srv.call(req).await.unwrap();
343        assert_eq!(resp.status(), StatusCode::OK);
344
345        let srv = init_service(App::new().service((
346            web::resource("/test1").to(async || HttpResponse::Ok()),
347            web::resource("/test2").to(async || HttpResponse::Ok()),
348        )))
349        .await;
350        let req = TestRequest::with_uri("/test1").to_request();
351        let resp = srv.call(req).await.unwrap();
352        assert_eq!(resp.status(), StatusCode::OK);
353        let req = TestRequest::with_uri("/test2").to_request();
354        let resp = srv.call(req).await.unwrap();
355        assert_eq!(resp.status(), StatusCode::OK);
356
357        let srv = init_service(App::new().service(vec![
358            web::resource("/test1").to(async || HttpResponse::Ok()),
359            web::resource("/test2").to(async || HttpResponse::Ok()),
360        ]))
361        .await;
362        let req = TestRequest::with_uri("/test1").to_request();
363        let resp = srv.call(req).await.unwrap();
364        assert_eq!(resp.status(), StatusCode::OK);
365        let req = TestRequest::with_uri("/test2").to_request();
366        let resp = srv.call(req).await.unwrap();
367        assert_eq!(resp.status(), StatusCode::OK);
368    }
369
370    #[test]
371    fn test_fmt_debug() {
372        let req = TestRequest::get()
373            .uri("/index.html?test=1")
374            .header("x-test", "111")
375            .to_srv_request();
376        let s = format!("{req:?}");
377        assert!(s.contains("WebRequest"));
378        assert!(s.contains("test=1"));
379        assert!(s.contains("x-test"));
380
381        let res = HttpResponse::Ok().header("x-test", "111").build();
382        let res = TestRequest::post()
383            .uri("/index.html?test=1")
384            .to_srv_response(res);
385
386        let s = format!("{res:?}");
387        assert!(s.contains("WebResponse"));
388        assert!(s.contains("x-test"));
389    }
390
391    #[crate::rt_test]
392    async fn test_service_empty_path_in_scope() {
393        let srv = init_service(App::new().service(web::scope("/app").service(
394            web::service("").build(async move |req: WebRequest<()>| {
395                Ok::<_, Infallible>(req.into_response(HttpResponse::Ok().build()))
396            }),
397        )))
398        .await;
399        let req = TestRequest::with_uri("/app").to_request();
400        let resp = srv.call(req).await.unwrap();
401        assert_eq!(resp.status(), StatusCode::OK);
402
403        let req = TestRequest::with_uri("/app/").to_request();
404        let resp = srv.call(req).await.unwrap();
405        assert_eq!(resp.status(), StatusCode::NOT_FOUND);
406    }
407}