Skip to main content

rama_http/service/web/
service.rs

1use crate::{
2    Body, Request, Response, StatusCode,
3    layer::error_handling::ErrorHandlerLayer,
4    matcher::HttpMatcher,
5    mime::Mime,
6    service::{
7        fs::{DirectoryServeMode, ServeDir},
8        web::{
9            IntoEndpointServiceWithState, endpoint::response::IntoResponse, response::ErrorResponse,
10        },
11    },
12};
13
14use rama_core::{
15    Layer,
16    extensions::Extensions,
17    extensions::ExtensionsRef,
18    matcher::Matcher,
19    service::{BoxService, Service, service_fn},
20    telemetry::tracing,
21};
22use rama_http_types::OriginalRouterUri;
23use rama_net::uri::PathMatchOptions;
24use rama_utils::{include_dir, str::arcstr::ArcStr};
25
26use std::{convert::Infallible, path::Path, sync::Arc};
27
28use super::{IntoEndpointService, endpoint::Endpoint};
29
30type DefaultLayer = ErrorHandlerLayer;
31
32/// A basic web service that can be used to serve HTTP requests.
33///
34/// Note that this service boxes all the internal services, so it is not as efficient as it could be.
35/// For those locations where you need do not desire the convenience over performance,
36/// you can instead use a tuple of `(M, S)` tuples, where M is a matcher and S is a service,
37/// e.g. `((MethodMatcher::GET, service_a), (MethodMatcher::POST, service_b), service_fallback)`.
38#[derive(Debug, Clone)]
39pub struct WebService<State = ()> {
40    endpoints: Vec<Arc<Endpoint>>,
41    not_found: BoxService<Request, Response, Infallible>,
42    state: State,
43}
44
45impl WebService {
46    #[must_use]
47    /// create a new web service
48    pub fn new() -> Self {
49        Self {
50            endpoints: Vec::new(),
51            not_found: service_fn(async || Ok(StatusCode::NOT_FOUND.into_response())).boxed(),
52            state: (),
53        }
54    }
55}
56
57impl<State> WebService<State>
58where
59    State: Send + Sync + Clone + 'static,
60{
61    pub fn new_with_state(state: State) -> Self {
62        Self {
63            endpoints: Vec::new(),
64            not_found: service_fn(async || Ok(StatusCode::NOT_FOUND.into_response())).boxed(),
65            state,
66        }
67    }
68
69    /// add a GET route to the web service, using the given service.
70    #[must_use]
71    #[inline]
72    pub fn with_get<I, T>(self, path: &str, service: I) -> Self
73    where
74        I: IntoEndpointServiceWithState<T, State>,
75        I::Service: Service<Request, Output: IntoResponse, Error: Into<ErrorResponse>>,
76    {
77        let matcher = HttpMatcher::method_get().and_path(path);
78        self.with_matcher(matcher, service)
79    }
80
81    /// add a GET route to the web service, using the given service.
82    #[inline]
83    pub fn set_get<I, T>(&mut self, path: &str, service: I) -> &mut Self
84    where
85        I: IntoEndpointServiceWithState<T, State>,
86        I::Service: Service<Request, Output: IntoResponse, Error: Into<ErrorResponse>>,
87    {
88        let matcher = HttpMatcher::method_get().and_path(path);
89        self.set_matcher(matcher, service)
90    }
91
92    /// add a POST route to the web service, using the given service.
93    #[must_use]
94    #[inline]
95    pub fn with_post<I, T>(self, path: &str, service: I) -> Self
96    where
97        I: IntoEndpointServiceWithState<T, State>,
98        I::Service: Service<Request, Output: IntoResponse, Error: Into<ErrorResponse>>,
99    {
100        let matcher = HttpMatcher::method_post().and_path(path);
101        self.with_matcher(matcher, service)
102    }
103
104    /// add a POST route to the web service, using the given service.
105    #[inline]
106    pub fn set_post<I, T>(&mut self, path: &str, service: I) -> &mut Self
107    where
108        I: IntoEndpointServiceWithState<T, State>,
109        I::Service: Service<Request, Output: IntoResponse, Error: Into<ErrorResponse>>,
110    {
111        let matcher = HttpMatcher::method_post().and_path(path);
112        self.set_matcher(matcher, service)
113    }
114
115    /// add a PUT route to the web service, using the given service.
116    #[must_use]
117    #[inline]
118    pub fn with_put<I, T>(self, path: &str, service: I) -> Self
119    where
120        I: IntoEndpointServiceWithState<T, State>,
121        I::Service: Service<Request, Output: IntoResponse, Error: Into<ErrorResponse>>,
122    {
123        let matcher = HttpMatcher::method_put().and_path(path);
124        self.with_matcher(matcher, service)
125    }
126
127    /// add a PUT route to the web service, using the given service.
128    #[inline]
129    pub fn set_put<I, T>(&mut self, path: &str, service: I) -> &mut Self
130    where
131        I: IntoEndpointServiceWithState<T, State>,
132        I::Service: Service<Request, Output: IntoResponse, Error: Into<ErrorResponse>>,
133    {
134        let matcher = HttpMatcher::method_put().and_path(path);
135        self.set_matcher(matcher, service)
136    }
137
138    /// add a DELETE route to the web service, using the given service.
139    #[must_use]
140    #[inline]
141    pub fn with_delete<I, T>(self, path: &str, service: I) -> Self
142    where
143        I: IntoEndpointServiceWithState<T, State>,
144        I::Service: Service<Request, Output: IntoResponse, Error: Into<ErrorResponse>>,
145    {
146        let matcher = HttpMatcher::method_delete().and_path(path);
147        self.with_matcher(matcher, service)
148    }
149
150    /// add a DELETE route to the web service, using the given service.
151    #[inline]
152    pub fn set_delete<I, T>(&mut self, path: &str, service: I) -> &mut Self
153    where
154        I: IntoEndpointServiceWithState<T, State>,
155        I::Service: Service<Request, Output: IntoResponse, Error: Into<ErrorResponse>>,
156    {
157        let matcher = HttpMatcher::method_delete().and_path(path);
158        self.set_matcher(matcher, service)
159    }
160
161    /// add a PATCH route to the web service, using the given service.
162    #[must_use]
163    #[inline]
164    pub fn with_patch<I, T>(self, path: &str, service: I) -> Self
165    where
166        I: IntoEndpointServiceWithState<T, State>,
167        I::Service: Service<Request, Output: IntoResponse, Error: Into<ErrorResponse>>,
168    {
169        let matcher = HttpMatcher::method_patch().and_path(path);
170        self.with_matcher(matcher, service)
171    }
172
173    /// add a PATCH route to the web service, using the given service.
174    #[inline]
175    pub fn set_patch<I, T>(&mut self, path: &str, service: I) -> &mut Self
176    where
177        I: IntoEndpointServiceWithState<T, State>,
178        I::Service: Service<Request, Output: IntoResponse, Error: Into<ErrorResponse>>,
179    {
180        let matcher = HttpMatcher::method_patch().and_path(path);
181        self.set_matcher(matcher, service)
182    }
183
184    /// add a HEAD route to the web service, using the given service.
185    #[must_use]
186    #[inline]
187    pub fn with_head<I, T>(self, path: &str, service: I) -> Self
188    where
189        I: IntoEndpointServiceWithState<T, State>,
190        I::Service: Service<Request, Output: IntoResponse, Error: Into<ErrorResponse>>,
191    {
192        let matcher = HttpMatcher::method_head().and_path(path);
193        self.with_matcher(matcher, service)
194    }
195
196    /// add a HEAD route to the web service, using the given service.
197    #[inline]
198    pub fn set_head<I, T>(&mut self, path: &str, service: I) -> &mut Self
199    where
200        I: IntoEndpointServiceWithState<T, State>,
201        I::Service: Service<Request, Output: IntoResponse, Error: Into<ErrorResponse>>,
202    {
203        let matcher = HttpMatcher::method_head().and_path(path);
204        self.set_matcher(matcher, service)
205    }
206
207    /// add a OPTIONS route to the web service, using the given service.
208    #[must_use]
209    #[inline]
210    pub fn with_options<I, T>(self, path: &str, service: I) -> Self
211    where
212        I: IntoEndpointServiceWithState<T, State>,
213        I::Service: Service<Request, Output: IntoResponse, Error: Into<ErrorResponse>>,
214    {
215        let matcher = HttpMatcher::method_options().and_path(path);
216        self.with_matcher(matcher, service)
217    }
218
219    /// add a OPTIONS route to the web service, using the given service.
220    #[inline]
221    pub fn set_options<I, T>(&mut self, path: &str, service: I) -> &mut Self
222    where
223        I: IntoEndpointServiceWithState<T, State>,
224        I::Service: Service<Request, Output: IntoResponse, Error: Into<ErrorResponse>>,
225    {
226        let matcher = HttpMatcher::method_options().and_path(path);
227        self.set_matcher(matcher, service)
228    }
229
230    /// add a TRACE route to the web service, using the given service.
231    #[must_use]
232    #[inline]
233    pub fn with_trace<I, T>(self, path: &str, service: I) -> Self
234    where
235        I: IntoEndpointServiceWithState<T, State>,
236        I::Service: Service<Request, Output: IntoResponse, Error: Into<ErrorResponse>>,
237    {
238        let matcher = HttpMatcher::method_trace().and_path(path);
239        self.with_matcher(matcher, service)
240    }
241
242    /// add a TRACE route to the web service, using the given service.
243    #[inline]
244    pub fn set_trace<I, T>(&mut self, path: &str, service: I) -> &mut Self
245    where
246        I: IntoEndpointServiceWithState<T, State>,
247        I::Service: Service<Request, Output: IntoResponse, Error: Into<ErrorResponse>>,
248    {
249        let matcher = HttpMatcher::method_trace().and_path(path);
250        self.set_matcher(matcher, service)
251    }
252
253    /// Nest a web service under the given path.
254    ///
255    /// The nested service will receive a request with the path prefix removed.
256    ///
257    /// Note: this sub-webservice is configured with the same State this router has.
258    #[must_use]
259    pub fn with_nest_make_fn(self, prefix: &str, configure_svc: impl FnOnce(Self) -> Self) -> Self {
260        let web_service = Self::new_with_state(self.state.clone());
261        let web_service = configure_svc(web_service);
262        self.with_nest_inner(prefix, web_service)
263    }
264
265    /// Nest a web service under the given path.
266    ///
267    /// The nested service will receive a request with the path prefix removed.
268    ///
269    /// Note: this sub-webservice is configured with the same State this router has.
270    pub fn set_nest_make_fn(
271        &mut self,
272        prefix: &str,
273        configure_svc: impl FnOnce(Self) -> Self,
274    ) -> &mut Self {
275        let web_service = Self::new_with_state(self.state.clone());
276        let web_service = configure_svc(web_service);
277        self.set_nest_inner(prefix, web_service)
278    }
279
280    /// Nest a web service under the given path.
281    ///
282    /// The nested service will receive a request with the path prefix removed.
283    ///
284    /// Warning: This sub-service has no notion of the state this webservice has. If you want
285    /// to create a nested-service that shares the same state this webservice has, use [WebService::set_nest_make_fn] instead.
286    #[must_use]
287    #[inline(always)]
288    pub fn with_nest_service<I, T>(self, prefix: impl AsRef<str>, service: I) -> Self
289    where
290        I: IntoEndpointService<T>,
291        I::Service: Service<Request, Output = Response, Error = Infallible>,
292    {
293        self.with_nest_inner(prefix, service.into_endpoint_service())
294    }
295
296    /// Nest a web service under the given path.
297    ///
298    /// The nested service will receive a request with the path prefix removed.
299    ///
300    /// Warning: This sub-service has no notion of the state this webservice has. If you want
301    /// to create a nested-service that shares the same state this webservice has, use [WebService::set_nest_make_fn] instead.
302    #[inline(always)]
303    pub fn set_nest_service<I, T>(&mut self, prefix: impl AsRef<str>, service: I) -> &mut Self
304    where
305        I: IntoEndpointService<T>,
306        I::Service: Service<Request, Output = Response, Error = Infallible>,
307    {
308        self.set_nest_inner(prefix, service.into_endpoint_service())
309    }
310
311    #[inline]
312    fn with_nest_inner<S>(mut self, prefix: impl AsRef<str>, inner: S) -> Self
313    where
314        S: Service<Request, Output = Response, Error = Infallible>,
315    {
316        self.set_nest_inner(prefix, inner);
317        self
318    }
319
320    fn set_nest_inner<S>(&mut self, prefix: impl AsRef<str>, inner: S) -> &mut Self
321    where
322        S: Service<Request, Output = Response, Error = Infallible>,
323    {
324        let prefix = prefix
325            .as_ref()
326            .trim_end_matches(['/', '*'])
327            .trim_start_matches('/');
328        let matcher = HttpMatcher::path_prefix(prefix);
329        let service = NestedService {
330            inner,
331            prefix: ArcStr::from(prefix),
332        };
333        self.set_matcher(matcher, service)
334    }
335
336    /// serve the given file under the given path.
337    #[must_use]
338    pub fn with_file(self, prefix: &str, path: impl AsRef<Path>, mime: Mime) -> Self {
339        let service = ServeDir::new_single_file(path, mime).fallback(self.not_found.clone());
340        self.with_nest_inner(prefix, service)
341    }
342
343    /// serve the given file under the given path.
344    pub fn set_file(&mut self, prefix: &str, path: impl AsRef<Path>, mime: Mime) -> &mut Self {
345        let service = ServeDir::new_single_file(path, mime).fallback(self.not_found.clone());
346        self.set_nest_inner(prefix, service)
347    }
348
349    /// serve the given directory under the given path.
350    #[inline]
351    #[must_use]
352    pub fn with_dir(self, prefix: &str, path: impl AsRef<Path>) -> Self {
353        self.with_dir_with_serve_mode(prefix, path, Default::default())
354    }
355
356    /// serve the given directory under the given path.
357    #[inline]
358    pub fn set_dir(&mut self, prefix: &str, path: impl AsRef<Path>) -> &mut Self {
359        self.set_dir_with_serve_mode(prefix, path, Default::default())
360    }
361
362    /// serve the given directory under the given path,
363    /// with a custom serve move.
364    #[must_use]
365    pub fn with_dir_with_serve_mode(
366        self,
367        prefix: &str,
368        path: impl AsRef<Path>,
369        mode: DirectoryServeMode,
370    ) -> Self {
371        let service = ServeDir::new(path)
372            .fallback(self.not_found.clone())
373            .with_directory_serve_mode(mode);
374        self.with_nest_service(prefix, service)
375    }
376
377    /// serve the given directory under the given path,
378    /// with a custom serve move.
379    pub fn set_dir_with_serve_mode(
380        &mut self,
381        prefix: &str,
382        path: impl AsRef<Path>,
383        mode: DirectoryServeMode,
384    ) -> &mut Self {
385        let service = ServeDir::new(path)
386            .fallback(self.not_found.clone())
387            .with_directory_serve_mode(mode);
388        self.set_nest_service(prefix, service)
389    }
390
391    /// serve the given embedded directory under the given path.
392    #[inline]
393    #[must_use]
394    pub fn with_dir_embed(self, prefix: &str, dir: include_dir::Dir<'static>) -> Self {
395        self.with_dir_embed_with_serve_mode(prefix, dir, Default::default())
396    }
397
398    /// serve the given embedded directory under the given path.
399    #[inline]
400    pub fn set_dir_embed(&mut self, prefix: &str, dir: include_dir::Dir<'static>) -> &mut Self {
401        self.set_dir_embed_with_serve_mode(prefix, dir, Default::default())
402    }
403
404    /// serve the given embedded directory under the given path
405    /// with a custom serve move.
406    #[must_use]
407    pub fn with_dir_embed_with_serve_mode(
408        self,
409        prefix: &str,
410        dir: include_dir::Dir<'static>,
411        mode: DirectoryServeMode,
412    ) -> Self {
413        let service = ServeDir::new_embedded(dir)
414            .fallback(self.not_found.clone())
415            .with_directory_serve_mode(mode);
416        self.with_nest_service(prefix, service)
417    }
418
419    /// serve the given embedded directory under the given path
420    /// with a custom serve move.
421    pub fn set_dir_embed_with_serve_mode(
422        &mut self,
423        prefix: &str,
424        dir: include_dir::Dir<'static>,
425        mode: DirectoryServeMode,
426    ) -> &mut Self {
427        let service = ServeDir::new_embedded(dir)
428            .fallback(self.not_found.clone())
429            .with_directory_serve_mode(mode);
430        self.set_nest_service(prefix, service)
431    }
432
433    /// add a route to the web service which matches the given matcher, using the given service.
434    #[must_use]
435    pub fn with_matcher<I, T>(mut self, matcher: HttpMatcher<Body>, service: I) -> Self
436    where
437        I: IntoEndpointServiceWithState<T, State>,
438        I::Service: Service<Request, Output: IntoResponse, Error: Into<ErrorResponse>>,
439    {
440        let endpoint = Endpoint {
441            matcher,
442            service: DefaultLayer::default()
443                .into_layer(service.into_endpoint_service_with_state(self.state.clone()))
444                .boxed(),
445        };
446        self.endpoints.push(Arc::new(endpoint));
447        self
448    }
449
450    /// add a route to the web service which matches the given matcher, using the given service.
451    pub fn set_matcher<I, T>(&mut self, matcher: HttpMatcher<Body>, service: I) -> &mut Self
452    where
453        I: IntoEndpointServiceWithState<T, State>,
454        I::Service: Service<Request, Output: IntoResponse, Error: Into<ErrorResponse>>,
455    {
456        let endpoint = Endpoint {
457            matcher,
458            service: DefaultLayer::default()
459                .into_layer(service.into_endpoint_service_with_state(self.state.clone()))
460                .boxed(),
461        };
462        self.endpoints.push(Arc::new(endpoint));
463        self
464    }
465
466    /// use the given service in case no match could be found.
467    #[must_use]
468    pub fn with_not_found<I, T>(mut self, service: I) -> Self
469    where
470        I: IntoEndpointServiceWithState<T, State>,
471        I::Service: Service<Request, Output: IntoResponse, Error: Into<ErrorResponse>>,
472    {
473        self.not_found = DefaultLayer::default()
474            .into_layer(service.into_endpoint_service_with_state(self.state.clone()))
475            .boxed();
476        self
477    }
478
479    /// use the given service in case no match could be found.
480    pub fn set_not_found<I, T>(&mut self, service: I) -> &mut Self
481    where
482        I: IntoEndpointServiceWithState<T, State>,
483        I::Service: Service<Request, Output: IntoResponse, Error: Into<ErrorResponse>>,
484    {
485        self.not_found = DefaultLayer::default()
486            .into_layer(service.into_endpoint_service_with_state(self.state.clone()))
487            .boxed();
488        self
489    }
490}
491
492#[derive(Debug, Clone)]
493struct NestedService<S> {
494    inner: S,
495    prefix: ArcStr,
496}
497
498impl<S> Service<Request> for NestedService<S>
499where
500    S: Service<Request>,
501{
502    type Output = S::Output;
503    type Error = S::Error;
504
505    fn serve(
506        &self,
507        req: Request,
508    ) -> impl Future<Output = Result<Self::Output, Self::Error>> + Send + '_ {
509        // set the nested path
510        let (mut parts, body) = req.into_parts();
511
512        let original_uri = parts.uri.clone();
513        if parts.uri.path_mut().strip_prefix_with_opts(
514            self.prefix.as_str(),
515            PathMatchOptions {
516                ignore_ascii_case: true,
517                ..Default::default()
518            },
519        ) {
520            if !parts.extensions.contains::<OriginalRouterUri>() {
521                parts.extensions.insert(OriginalRouterUri(original_uri));
522            }
523        } else {
524            tracing::debug!(
525                "failed to strip prefix '{}' from Uri (bug??) preserve og uri as is",
526                self.prefix,
527            );
528        }
529
530        let req = Request::from_parts(parts, body);
531
532        // make the actual request
533        self.inner.serve(req)
534    }
535}
536
537impl Default for WebService {
538    fn default() -> Self {
539        Self::new()
540    }
541}
542
543impl<State> Service<Request> for WebService<State>
544where
545    State: Send + Sync + Clone + 'static,
546{
547    type Output = Response;
548    type Error = Infallible;
549
550    async fn serve(&self, req: Request) -> Result<Self::Output, Self::Error> {
551        for endpoint in &self.endpoints {
552            let ext = Extensions::new();
553            if endpoint.matcher.matches(Some(&ext), &req) {
554                // insert the extensions that might be generated by the matcher(s) into the context
555                req.extensions().extend(&ext);
556                return endpoint.service.serve(req).await;
557            }
558        }
559        self.not_found.serve(req).await
560    }
561}
562
563#[doc(hidden)]
564#[macro_export]
565/// Create a new [`Service`] from a chain of matcher-service tuples.
566///
567/// Think of it like the Rust match statement, but for http services.
568/// Which is nothing more then a convenient wrapper to create a tuple of matcher-service tuples,
569/// with the last tuple being the fallback service. And all services implement
570/// the [`IntoEndpointService`] trait.
571///
572/// # Example
573///
574/// ```rust
575/// use rama_http::matcher::{HttpMatcher, MethodMatcher};
576/// use rama_http::{Body, Request, Response, StatusCode};
577/// use rama_http::body::util::BodyExt;
578/// use rama_core::{Service};
579///
580/// #[tokio::main]
581/// async fn main() {
582///   let svc = rama_http::service::web::match_service! {
583///     HttpMatcher::get("/hello") => "hello",
584///     HttpMatcher::post("/world") => "world",
585///     MethodMatcher::CONNECT => "connect",
586///     _ => StatusCode::NOT_FOUND,
587///   };
588///
589///   let resp = svc.serve(
590///       Request::post("https://www.test.io/world").body(Body::empty()).unwrap(),
591///   ).await.unwrap();
592///   assert_eq!(resp.status(), StatusCode::OK);
593///   let body = resp.into_body().collect().await.unwrap().to_bytes();
594///   assert_eq!(body, "world");
595/// }
596/// ```
597///
598/// Which is short for the following:
599///
600/// ```rust
601/// use rama_http::matcher::{HttpMatcher, MethodMatcher};
602/// use rama_http::{Body, Request, Response, StatusCode};
603/// use rama_http::body::util::BodyExt;
604/// use rama_http::service::web::IntoEndpointService;
605/// use rama_http::layer::error_handling::ErrorHandler;
606/// use rama_core::{Service};
607/// use rama_core::matcher::MatcherRouter;
608///
609/// #[tokio::main]
610/// async fn main() {
611///   let svc = MatcherRouter((
612///     (HttpMatcher::get("/hello"), ErrorHandler::new("hello".into_endpoint_service())),
613///     (HttpMatcher::post("/world"), ErrorHandler::new("world".into_endpoint_service())),
614///     (MethodMatcher::CONNECT, ErrorHandler::new("connect".into_endpoint_service())),
615///     ErrorHandler::new(StatusCode::NOT_FOUND.into_endpoint_service()),
616///   ));
617///
618///   let resp = svc.serve(
619///
620///      Request::post("https://www.test.io/world").body(Body::empty()).unwrap(),
621///   ).await.unwrap();
622///   assert_eq!(resp.status(), StatusCode::OK);
623///   let body = resp.into_body().collect().await.unwrap().to_bytes();
624///   assert_eq!(body, "world");
625/// }
626/// ```
627///
628/// As you can see it is pretty much the same, except that you need to explicitly ensure
629/// that each service is an actual Endpoint service.
630macro_rules! __match_service {
631    ($($M:expr_2021 => $S:expr_2021),+, _ => $F:expr $(,)?) => {{
632        use $crate::service::web::IntoEndpointService;
633        use $crate::layer::error_handling::ErrorHandler;
634        use $crate::__macro_dep::__core::matcher::MatcherRouter;
635        MatcherRouter((
636            $(($M, ErrorHandler::new($S.into_endpoint_service()))),+,
637            ErrorHandler::new($F.into_endpoint_service()))
638        )
639    }};
640}
641
642#[doc(inline)]
643pub use crate::__match_service as match_service;
644
645#[cfg(test)]
646mod test {
647    use crate::matcher::MethodMatcher;
648    use crate::service::web::extract::State;
649    use crate::{Body, body::util::BodyExt};
650
651    use super::*;
652
653    async fn get_response<S>(service: &S, uri: &str) -> Response
654    where
655        S: Service<Request, Output = Response, Error = Infallible>,
656    {
657        let req = Request::get(uri).body(Body::empty()).unwrap();
658        service.serve(req).await.unwrap()
659    }
660
661    async fn post_response<S>(service: &S, uri: &str) -> Response
662    where
663        S: Service<Request, Output = Response, Error = Infallible>,
664    {
665        let req = Request::post(uri).body(Body::empty()).unwrap();
666        service.serve(req).await.unwrap()
667    }
668
669    async fn connect_response<S>(service: &S, uri: &str) -> Response
670    where
671        S: Service<Request, Output = Response, Error = Infallible>,
672    {
673        let req = Request::connect(uri).body(Body::empty()).unwrap();
674        service.serve(req).await.unwrap()
675    }
676
677    #[tokio::test]
678    async fn test_web_service() {
679        let svc = WebService::new()
680            .with_get("/hello", "hello")
681            .with_post("/world", "world");
682
683        let res = get_response(&svc, "https://www.test.io/hello").await;
684        assert_eq!(res.status(), StatusCode::OK);
685        let body = res.into_body().collect().await.unwrap().to_bytes();
686        assert_eq!(body, "hello");
687
688        let res = post_response(&svc, "https://www.test.io/world").await;
689        assert_eq!(res.status(), StatusCode::OK);
690        let body = res.into_body().collect().await.unwrap().to_bytes();
691        assert_eq!(body, "world");
692
693        let res = get_response(&svc, "https://www.test.io/world").await;
694        assert_eq!(res.status(), StatusCode::NOT_FOUND);
695
696        let res = get_response(&svc, "https://www.test.io").await;
697        assert_eq!(res.status(), StatusCode::NOT_FOUND);
698    }
699
700    #[tokio::test]
701    async fn test_web_service_not_found() {
702        let svc = WebService::new().with_not_found("not found");
703
704        let res = get_response(&svc, "https://www.test.io/hello").await;
705        assert_eq!(res.status(), StatusCode::OK);
706        let body = res.into_body().collect().await.unwrap().to_bytes();
707        assert_eq!(body, "not found");
708    }
709
710    #[tokio::test]
711    async fn test_web_service_nest() {
712        let state = "state".to_owned();
713
714        let svc = WebService::new_with_state(state)
715            .with_get("/state", async |State(state): State<String>| state)
716            .with_nest_make_fn("/api", |web| {
717                web.with_get("/hello", "hello")
718                    .with_post("/world", "world")
719                    .with_get("/state", async |State(state): State<String>| state)
720            });
721
722        let res = get_response(&svc, "https://www.test.io/api/hello").await;
723        assert_eq!(res.status(), StatusCode::OK);
724        let body = res.into_body().collect().await.unwrap().to_bytes();
725        assert_eq!(body, "hello");
726
727        let res = post_response(&svc, "https://www.test.io/api/world").await;
728        assert_eq!(res.status(), StatusCode::OK);
729        let body = res.into_body().collect().await.unwrap().to_bytes();
730        assert_eq!(body, "world");
731
732        let res = get_response(&svc, "https://www.test.io/api/world").await;
733        assert_eq!(res.status(), StatusCode::NOT_FOUND);
734
735        let res = get_response(&svc, "https://www.test.io").await;
736        assert_eq!(res.status(), StatusCode::NOT_FOUND);
737
738        let res = get_response(&svc, "https://www.test.io/state").await;
739        assert_eq!(res.status(), StatusCode::OK);
740        let body = res.into_body().collect().await.unwrap().to_bytes();
741        assert_eq!(body, "state");
742
743        let res = get_response(&svc, "https://www.test.io/api/state").await;
744        assert_eq!(res.status(), StatusCode::OK);
745        let body = res.into_body().collect().await.unwrap().to_bytes();
746        assert_eq!(body, "state");
747    }
748
749    #[tokio::test]
750    async fn test_web_service_dir() {
751        let tmp_dir = tempfile::tempdir().unwrap();
752        let file_path = tmp_dir.path().join("index.html");
753        std::fs::write(&file_path, "<h1>Hello, World!</h1>").unwrap();
754        let style_dir = tmp_dir.path().join("style");
755        std::fs::create_dir(&style_dir).unwrap();
756        let file_path = style_dir.join("main.css");
757        std::fs::write(&file_path, "body { background-color: red }").unwrap();
758
759        let svc = WebService::new()
760            .with_get("/api/version", "v1")
761            .with_post("/api", StatusCode::FORBIDDEN)
762            .with_dir("/", tmp_dir.path().to_str().unwrap());
763
764        let res = get_response(&svc, "https://www.test.io/index.html").await;
765        assert_eq!(res.status(), StatusCode::OK);
766        let body = res.into_body().collect().await.unwrap().to_bytes();
767        assert_eq!(body, "<h1>Hello, World!</h1>");
768
769        let res = get_response(&svc, "https://www.test.io/style/main.css").await;
770        assert_eq!(res.status(), StatusCode::OK);
771        let body = res.into_body().collect().await.unwrap().to_bytes();
772        assert_eq!(body, "body { background-color: red }");
773
774        let res = get_response(&svc, "https://www.test.io/api/version").await;
775        assert_eq!(res.status(), StatusCode::OK);
776        let body = res.into_body().collect().await.unwrap().to_bytes();
777        assert_eq!(body, "v1");
778
779        let res = post_response(&svc, "https://www.test.io/api").await;
780        assert_eq!(res.status(), StatusCode::FORBIDDEN);
781
782        let res = get_response(&svc, "https://www.test.io/notfound.html").await;
783        assert_eq!(res.status(), StatusCode::NOT_FOUND);
784
785        let res = get_response(&svc, "https://www.test.io/").await;
786        assert_eq!(res.status(), StatusCode::OK);
787        let body = res.into_body().collect().await.unwrap().to_bytes();
788        assert_eq!(body, "<h1>Hello, World!</h1>");
789    }
790
791    #[tokio::test]
792    async fn test_matcher_service_tuples() {
793        let svc = match_service! {
794            HttpMatcher::get("/hello") => "hello",
795            HttpMatcher::post("/world") => "world",
796            MethodMatcher::CONNECT => "connect",
797            _ => StatusCode::NOT_FOUND,
798        };
799
800        let res = get_response(&svc, "https://www.test.io/hello").await;
801        assert_eq!(res.status(), StatusCode::OK);
802        let body = res.into_body().collect().await.unwrap().to_bytes();
803        assert_eq!(body, "hello");
804
805        let res = post_response(&svc, "https://www.test.io/world").await;
806        assert_eq!(res.status(), StatusCode::OK);
807        let body = res.into_body().collect().await.unwrap().to_bytes();
808        assert_eq!(body, "world");
809
810        let res = connect_response(&svc, "https://www.test.io").await;
811        assert_eq!(res.status(), StatusCode::OK);
812        let body = res.into_body().collect().await.unwrap().to_bytes();
813        assert_eq!(body, "connect");
814
815        let res = get_response(&svc, "https://www.test.io/world").await;
816        assert_eq!(res.status(), StatusCode::NOT_FOUND);
817
818        let res = get_response(&svc, "https://www.test.io").await;
819        assert_eq!(res.status(), StatusCode::NOT_FOUND);
820    }
821}