rama_http/service/client/
ext.rs

1use crate::{Method, Request, Response, Uri, headers::HeaderExt};
2use rama_core::{
3    Context, Service,
4    error::{BoxError, ErrorExt, OpaqueError},
5};
6
7/// Extends an Http Client with high level features,
8/// to facilitate the creation and sending of http requests,
9/// in a more ergonomic way.
10pub trait HttpClientExt<State>:
11    private::HttpClientExtSealed<State> + Sized + Send + Sync + 'static
12{
13    /// The response type returned by the `execute` method.
14    type ExecuteResponse;
15    /// The error type returned by the `execute` method.
16    type ExecuteError;
17
18    /// Convenience method to make a `GET` request to a URL.
19    ///
20    /// # Errors
21    ///
22    /// This method fails whenever the supplied [`Url`] cannot be parsed.
23    ///
24    /// [`Url`]: crate::Uri
25    fn get(&self, url: impl IntoUrl) -> RequestBuilder<Self, State, Self::ExecuteResponse>;
26
27    /// Convenience method to make a `POST` request to a URL.
28    ///
29    /// # Errors
30    ///
31    /// This method fails whenever the supplied [`Url`] cannot be parsed.
32    ///
33    /// [`Url`]: crate::Uri
34    fn post(&self, url: impl IntoUrl) -> RequestBuilder<Self, State, Self::ExecuteResponse>;
35
36    /// Convenience method to make a `PUT` request to a URL.
37    ///
38    /// # Errors
39    ///
40    /// This method fails whenever the supplied [`Url`] cannot be parsed.
41    ///
42    /// [`Url`]: crate::Uri
43    fn put(&self, url: impl IntoUrl) -> RequestBuilder<Self, State, Self::ExecuteResponse>;
44
45    /// Convenience method to make a `PATCH` request to a URL.
46    ///
47    /// # Errors
48    ///
49    /// This method fails whenever the supplied [`Url`] cannot be parsed.
50    ///
51    /// [`Url`]: crate::Uri
52    fn patch(&self, url: impl IntoUrl) -> RequestBuilder<Self, State, Self::ExecuteResponse>;
53
54    /// Convenience method to make a `DELETE` request to a URL.
55    ///
56    /// # Errors
57    ///
58    /// This method fails whenever the supplied [`Url`] cannot be parsed.
59    ///
60    /// [`Url`]: crate::Uri
61    fn delete(&self, url: impl IntoUrl) -> RequestBuilder<Self, State, Self::ExecuteResponse>;
62
63    /// Convenience method to make a `HEAD` request to a URL.
64    ///
65    /// # Errors
66    ///
67    /// This method fails whenever the supplied `Url` cannot be parsed.
68    fn head(&self, url: impl IntoUrl) -> RequestBuilder<Self, State, Self::ExecuteResponse>;
69
70    /// Start building a [`Request`] with the [`Method`] and [`Url`].
71    ///
72    /// Returns a [`RequestBuilder`], which will allow setting headers and
73    /// the request body before sending.
74    ///
75    /// [`Request`]: crate::Request
76    /// [`Method`]: crate::Method
77    /// [`Url`]: crate::Uri
78    ///
79    /// # Errors
80    ///
81    /// This method fails whenever the supplied [`Url`] cannot be parsed.
82    fn request(
83        &self,
84        method: Method,
85        url: impl IntoUrl,
86    ) -> RequestBuilder<Self, State, Self::ExecuteResponse>;
87
88    /// Executes a `Request`.
89    ///
90    /// # Errors
91    ///
92    /// This method fails if there was an error while sending request.
93    fn execute(
94        &self,
95        ctx: Context<State>,
96        request: Request,
97    ) -> impl Future<Output = Result<Self::ExecuteResponse, Self::ExecuteError>>;
98}
99
100impl<State, S, Body> HttpClientExt<State> for S
101where
102    S: Service<State, Request, Response = Response<Body>, Error: Into<BoxError>>,
103{
104    type ExecuteResponse = Response<Body>;
105    type ExecuteError = S::Error;
106
107    fn get(&self, url: impl IntoUrl) -> RequestBuilder<Self, State, Self::ExecuteResponse> {
108        self.request(Method::GET, url)
109    }
110
111    fn post(&self, url: impl IntoUrl) -> RequestBuilder<Self, State, Self::ExecuteResponse> {
112        self.request(Method::POST, url)
113    }
114
115    fn put(&self, url: impl IntoUrl) -> RequestBuilder<Self, State, Self::ExecuteResponse> {
116        self.request(Method::PUT, url)
117    }
118
119    fn patch(&self, url: impl IntoUrl) -> RequestBuilder<Self, State, Self::ExecuteResponse> {
120        self.request(Method::PATCH, url)
121    }
122
123    fn delete(&self, url: impl IntoUrl) -> RequestBuilder<Self, State, Self::ExecuteResponse> {
124        self.request(Method::DELETE, url)
125    }
126
127    fn head(&self, url: impl IntoUrl) -> RequestBuilder<Self, State, Self::ExecuteResponse> {
128        self.request(Method::HEAD, url)
129    }
130
131    fn request(
132        &self,
133        method: Method,
134        url: impl IntoUrl,
135    ) -> RequestBuilder<Self, State, Self::ExecuteResponse> {
136        let uri = match url.into_url() {
137            Ok(uri) => uri,
138            Err(err) => {
139                return RequestBuilder {
140                    http_client_service: self,
141                    state: RequestBuilderState::Error(err),
142                    _phantom: std::marker::PhantomData,
143                };
144            }
145        };
146
147        let builder = crate::dep::http::request::Builder::new()
148            .method(method)
149            .uri(uri);
150
151        RequestBuilder {
152            http_client_service: self,
153            state: RequestBuilderState::PreBody(builder),
154            _phantom: std::marker::PhantomData,
155        }
156    }
157
158    fn execute(
159        &self,
160        ctx: Context<State>,
161        request: Request,
162    ) -> impl Future<Output = Result<Self::ExecuteResponse, Self::ExecuteError>> {
163        Service::serve(self, ctx, request)
164    }
165}
166
167/// A trait to try to convert some type into a [`Url`].
168///
169/// This trait is “sealed”, such that only types within rama can implement it.
170///
171/// [`Url`]: crate::Uri
172pub trait IntoUrl: private::IntoUrlSealed {}
173
174impl IntoUrl for Uri {}
175impl IntoUrl for &str {}
176impl IntoUrl for String {}
177impl IntoUrl for &String {}
178
179/// A trait to try to convert some type into a [`HeaderName`].
180///
181/// This trait is “sealed”, such that only types within rama can implement it.
182///
183/// [`HeaderName`]: crate::HeaderName
184pub trait IntoHeaderName: private::IntoHeaderNameSealed {}
185
186impl IntoHeaderName for crate::HeaderName {}
187impl IntoHeaderName for Option<crate::HeaderName> {}
188impl IntoHeaderName for &str {}
189impl IntoHeaderName for String {}
190impl IntoHeaderName for &String {}
191impl IntoHeaderName for &[u8] {}
192
193/// A trait to try to convert some type into a [`HeaderValue`].
194///
195/// This trait is “sealed”, such that only types within rama can implement it.
196///
197/// [`HeaderValue`]: crate::HeaderValue
198pub trait IntoHeaderValue: private::IntoHeaderValueSealed {}
199
200impl IntoHeaderValue for crate::HeaderValue {}
201impl IntoHeaderValue for &str {}
202impl IntoHeaderValue for String {}
203impl IntoHeaderValue for &String {}
204impl IntoHeaderValue for &[u8] {}
205
206mod private {
207    use rama_http_types::HeaderName;
208    use rama_net::Protocol;
209
210    use super::*;
211
212    pub trait IntoUrlSealed {
213        fn into_url(self) -> Result<Uri, OpaqueError>;
214    }
215
216    impl IntoUrlSealed for Uri {
217        fn into_url(self) -> Result<Uri, OpaqueError> {
218            let protocol: Option<Protocol> = self.scheme().map(Into::into);
219            match protocol {
220                Some(protocol) => {
221                    if protocol.is_http() {
222                        Ok(self)
223                    } else {
224                        Err(OpaqueError::from_display(format!(
225                            "Unsupported protocol: {protocol}"
226                        )))
227                    }
228                }
229                None => Err(OpaqueError::from_display("Missing scheme in URI")),
230            }
231        }
232    }
233
234    impl IntoUrlSealed for &str {
235        fn into_url(self) -> Result<Uri, OpaqueError> {
236            match self.parse::<Uri>() {
237                Ok(uri) => uri.into_url(),
238                Err(_) => Err(OpaqueError::from_display(format!("Invalid URL: {}", self))),
239            }
240        }
241    }
242
243    impl IntoUrlSealed for String {
244        fn into_url(self) -> Result<Uri, OpaqueError> {
245            self.as_str().into_url()
246        }
247    }
248
249    impl IntoUrlSealed for &String {
250        fn into_url(self) -> Result<Uri, OpaqueError> {
251            self.as_str().into_url()
252        }
253    }
254
255    pub trait IntoHeaderNameSealed {
256        fn into_header_name(self) -> Result<crate::HeaderName, OpaqueError>;
257    }
258
259    impl IntoHeaderNameSealed for HeaderName {
260        fn into_header_name(self) -> Result<crate::HeaderName, OpaqueError> {
261            Ok(self)
262        }
263    }
264
265    impl IntoHeaderNameSealed for Option<HeaderName> {
266        fn into_header_name(self) -> Result<crate::HeaderName, OpaqueError> {
267            match self {
268                Some(name) => Ok(name),
269                None => Err(OpaqueError::from_display("Header name is required")),
270            }
271        }
272    }
273
274    impl IntoHeaderNameSealed for &str {
275        fn into_header_name(self) -> Result<crate::HeaderName, OpaqueError> {
276            let name = self
277                .parse::<crate::HeaderName>()
278                .map_err(OpaqueError::from_std)?;
279            Ok(name)
280        }
281    }
282
283    impl IntoHeaderNameSealed for String {
284        fn into_header_name(self) -> Result<crate::HeaderName, OpaqueError> {
285            self.as_str().into_header_name()
286        }
287    }
288
289    impl IntoHeaderNameSealed for &String {
290        fn into_header_name(self) -> Result<crate::HeaderName, OpaqueError> {
291            self.as_str().into_header_name()
292        }
293    }
294
295    impl IntoHeaderNameSealed for &[u8] {
296        fn into_header_name(self) -> Result<crate::HeaderName, OpaqueError> {
297            let name = crate::HeaderName::from_bytes(self).map_err(OpaqueError::from_std)?;
298            Ok(name)
299        }
300    }
301
302    pub trait IntoHeaderValueSealed {
303        fn into_header_value(self) -> Result<crate::HeaderValue, OpaqueError>;
304    }
305
306    impl IntoHeaderValueSealed for crate::HeaderValue {
307        fn into_header_value(self) -> Result<crate::HeaderValue, OpaqueError> {
308            Ok(self)
309        }
310    }
311
312    impl IntoHeaderValueSealed for &str {
313        fn into_header_value(self) -> Result<crate::HeaderValue, OpaqueError> {
314            let value = self
315                .parse::<crate::HeaderValue>()
316                .map_err(OpaqueError::from_std)?;
317            Ok(value)
318        }
319    }
320
321    impl IntoHeaderValueSealed for String {
322        fn into_header_value(self) -> Result<crate::HeaderValue, OpaqueError> {
323            self.as_str().into_header_value()
324        }
325    }
326
327    impl IntoHeaderValueSealed for &String {
328        fn into_header_value(self) -> Result<crate::HeaderValue, OpaqueError> {
329            self.as_str().into_header_value()
330        }
331    }
332
333    impl IntoHeaderValueSealed for &[u8] {
334        fn into_header_value(self) -> Result<crate::HeaderValue, OpaqueError> {
335            let value = crate::HeaderValue::from_bytes(self).map_err(OpaqueError::from_std)?;
336            Ok(value)
337        }
338    }
339
340    pub trait HttpClientExtSealed<State> {}
341
342    impl<State, S, Body> HttpClientExtSealed<State> for S where
343        S: Service<State, Request, Response = Response<Body>, Error: Into<BoxError>>
344    {
345    }
346}
347
348/// A builder to construct the properties of a [`Request`].
349///
350/// Constructed using [`HttpClientExt`].
351pub struct RequestBuilder<'a, S, State, Response> {
352    http_client_service: &'a S,
353    state: RequestBuilderState,
354    _phantom: std::marker::PhantomData<fn(State, Response) -> ()>,
355}
356
357impl<S, State, Response> std::fmt::Debug for RequestBuilder<'_, S, State, Response>
358where
359    S: std::fmt::Debug,
360{
361    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
362        f.debug_struct("RequestBuilder")
363            .field("http_client_service", &self.http_client_service)
364            .field("state", &self.state)
365            .finish()
366    }
367}
368
369#[derive(Debug)]
370enum RequestBuilderState {
371    PreBody(crate::dep::http::request::Builder),
372    PostBody(crate::Request),
373    Error(OpaqueError),
374}
375
376impl<S, State, Body> RequestBuilder<'_, S, State, Response<Body>>
377where
378    S: Service<State, Request, Response = Response<Body>, Error: Into<BoxError>>,
379{
380    /// Add a `Header` to this [`Request`].
381    pub fn header<K, V>(mut self, key: K, value: V) -> Self
382    where
383        K: IntoHeaderName,
384        V: IntoHeaderValue,
385    {
386        match self.state {
387            RequestBuilderState::PreBody(builder) => {
388                let key = match key.into_header_name() {
389                    Ok(key) => key,
390                    Err(err) => {
391                        self.state = RequestBuilderState::Error(err);
392                        return self;
393                    }
394                };
395                let value = match value.into_header_value() {
396                    Ok(value) => value,
397                    Err(err) => {
398                        self.state = RequestBuilderState::Error(err);
399                        return self;
400                    }
401                };
402                self.state = RequestBuilderState::PreBody(builder.header(key, value));
403                self
404            }
405            RequestBuilderState::PostBody(mut request) => {
406                let key = match key.into_header_name() {
407                    Ok(key) => key,
408                    Err(err) => {
409                        self.state = RequestBuilderState::Error(err);
410                        return self;
411                    }
412                };
413                let value = match value.into_header_value() {
414                    Ok(value) => value,
415                    Err(err) => {
416                        self.state = RequestBuilderState::Error(err);
417                        return self;
418                    }
419                };
420                request.headers_mut().append(key, value);
421                self.state = RequestBuilderState::PostBody(request);
422                self
423            }
424            RequestBuilderState::Error(err) => {
425                self.state = RequestBuilderState::Error(err);
426                self
427            }
428        }
429    }
430
431    /// Add a typed [`Header`] to this [`Request`].
432    ///
433    /// [`Header`]: crate::headers::Header
434    pub fn typed_header<H>(self, header: H) -> Self
435    where
436        H: crate::headers::Header,
437    {
438        self.header(H::name().clone(), header.encode_to_value())
439    }
440
441    /// Add all `Headers` from the [`HeaderMap`] to this [`Request`].
442    ///
443    /// [`HeaderMap`]: crate::HeaderMap
444    pub fn headers(mut self, headers: crate::HeaderMap) -> Self {
445        for (key, value) in headers.into_iter() {
446            self = self.header(key, value);
447        }
448        self
449    }
450
451    /// Enable HTTP basic authentication.
452    pub fn basic_auth<U, P>(self, username: U, password: P) -> Self
453    where
454        U: AsRef<str>,
455        P: AsRef<str>,
456    {
457        let header = crate::headers::Authorization::basic(username.as_ref(), password.as_ref());
458        self.typed_header(header)
459    }
460
461    /// Enable HTTP bearer authentication.
462    pub fn bearer_auth<T>(mut self, token: T) -> Self
463    where
464        T: AsRef<str>,
465    {
466        let header = match crate::headers::Authorization::bearer(token.as_ref()) {
467            Ok(header) => header,
468            Err(err) => {
469                self.state = match self.state {
470                    RequestBuilderState::Error(original_err) => {
471                        RequestBuilderState::Error(original_err)
472                    }
473                    _ => RequestBuilderState::Error(OpaqueError::from_std(err)),
474                };
475                return self;
476            }
477        };
478
479        self.typed_header(header)
480    }
481
482    /// Set the [`Request`]'s [`Body`].
483    ///
484    /// [`Body`]: crate::Body
485    pub fn body<T>(mut self, body: T) -> Self
486    where
487        T: TryInto<crate::Body, Error: Into<BoxError>>,
488    {
489        self.state = match self.state {
490            RequestBuilderState::PreBody(builder) => match body.try_into() {
491                Ok(body) => match builder.body(body) {
492                    Ok(req) => RequestBuilderState::PostBody(req),
493                    Err(err) => RequestBuilderState::Error(OpaqueError::from_std(err)),
494                },
495                Err(err) => RequestBuilderState::Error(OpaqueError::from_boxed(err.into())),
496            },
497            RequestBuilderState::PostBody(mut req) => match body.try_into() {
498                Ok(body) => {
499                    *req.body_mut() = body;
500                    RequestBuilderState::PostBody(req)
501                }
502                Err(err) => RequestBuilderState::Error(OpaqueError::from_boxed(err.into())),
503            },
504            RequestBuilderState::Error(err) => RequestBuilderState::Error(err),
505        };
506        self
507    }
508
509    /// Set the given value as a URL-Encoded Form [`Body`] in the [`Request`].
510    ///
511    /// [`Body`]: crate::Body
512    pub fn form<T: serde::Serialize + ?Sized>(mut self, form: &T) -> Self {
513        self.state = match self.state {
514            RequestBuilderState::PreBody(mut builder) => match serde_html_form::to_string(form) {
515                Ok(body) => {
516                    let builder = match builder.headers_mut() {
517                        Some(headers) => {
518                            if !headers.contains_key(crate::header::CONTENT_TYPE) {
519                                headers.insert(
520                                    crate::header::CONTENT_TYPE,
521                                    crate::HeaderValue::from_static(
522                                        "application/x-www-form-urlencoded",
523                                    ),
524                                );
525                            }
526                            builder
527                        }
528                        None => builder.header(
529                            crate::header::CONTENT_TYPE,
530                            crate::HeaderValue::from_static("application/x-www-form-urlencoded"),
531                        ),
532                    };
533                    match builder.body(body.into()) {
534                        Ok(req) => RequestBuilderState::PostBody(req),
535                        Err(err) => RequestBuilderState::Error(OpaqueError::from_std(err)),
536                    }
537                }
538                Err(err) => RequestBuilderState::Error(OpaqueError::from_std(err)),
539            },
540            RequestBuilderState::PostBody(mut req) => match serde_html_form::to_string(form) {
541                Ok(body) => {
542                    if !req.headers().contains_key(crate::header::CONTENT_TYPE) {
543                        req.headers_mut().insert(
544                            crate::header::CONTENT_TYPE,
545                            crate::HeaderValue::from_static("application/x-www-form-urlencoded"),
546                        );
547                    }
548                    *req.body_mut() = body.into();
549                    RequestBuilderState::PostBody(req)
550                }
551                Err(err) => RequestBuilderState::Error(OpaqueError::from_std(err)),
552            },
553            RequestBuilderState::Error(err) => RequestBuilderState::Error(err),
554        };
555        self
556    }
557
558    /// Set the given value as a JSON [`Body`] in the [`Request`].
559    ///
560    /// [`Body`]: crate::Body
561    pub fn json<T: serde::Serialize + ?Sized>(mut self, json: &T) -> Self {
562        self.state = match self.state {
563            RequestBuilderState::PreBody(mut builder) => match serde_json::to_vec(json) {
564                Ok(body) => {
565                    let builder = match builder.headers_mut() {
566                        Some(headers) => {
567                            if !headers.contains_key(crate::header::CONTENT_TYPE) {
568                                headers.insert(
569                                    crate::header::CONTENT_TYPE,
570                                    crate::HeaderValue::from_static("application/json"),
571                                );
572                            }
573                            builder
574                        }
575                        None => builder.header(
576                            crate::header::CONTENT_TYPE,
577                            crate::HeaderValue::from_static("application/json"),
578                        ),
579                    };
580                    match builder.body(body.into()) {
581                        Ok(req) => RequestBuilderState::PostBody(req),
582                        Err(err) => RequestBuilderState::Error(OpaqueError::from_std(err)),
583                    }
584                }
585                Err(err) => RequestBuilderState::Error(OpaqueError::from_std(err)),
586            },
587            RequestBuilderState::PostBody(mut req) => match serde_json::to_vec(json) {
588                Ok(body) => {
589                    if !req.headers().contains_key(crate::header::CONTENT_TYPE) {
590                        req.headers_mut().insert(
591                            crate::header::CONTENT_TYPE,
592                            crate::HeaderValue::from_static("application/json"),
593                        );
594                    }
595                    *req.body_mut() = body.into();
596                    RequestBuilderState::PostBody(req)
597                }
598                Err(err) => RequestBuilderState::Error(OpaqueError::from_std(err)),
599            },
600            RequestBuilderState::Error(err) => RequestBuilderState::Error(err),
601        };
602        self
603    }
604
605    /// Set the http [`Version`] of this [`Request`].
606    ///
607    /// [`Version`]: crate::Version
608    pub fn version(mut self, version: crate::Version) -> Self {
609        match self.state {
610            RequestBuilderState::PreBody(builder) => {
611                self.state = RequestBuilderState::PreBody(builder.version(version));
612                self
613            }
614            RequestBuilderState::PostBody(mut request) => {
615                *request.version_mut() = version;
616                self.state = RequestBuilderState::PostBody(request);
617                self
618            }
619            RequestBuilderState::Error(err) => {
620                self.state = RequestBuilderState::Error(err);
621                self
622            }
623        }
624    }
625
626    /// Constructs the [`Request`] and sends it to the target [`Uri`], returning a future [`Response`].
627    ///
628    /// # Errors
629    ///
630    /// This method fails if there was an error while sending [`Request`].
631    pub async fn send(self, ctx: Context<State>) -> Result<Response<Body>, OpaqueError> {
632        let request = match self.state {
633            RequestBuilderState::PreBody(builder) => builder
634                .body(crate::Body::empty())
635                .map_err(OpaqueError::from_std)?,
636            RequestBuilderState::PostBody(request) => request,
637            RequestBuilderState::Error(err) => return Err(err),
638        };
639
640        let uri = request.uri().clone();
641        match self.http_client_service.serve(ctx, request).await {
642            Ok(response) => Ok(response),
643            Err(err) => Err(OpaqueError::from_boxed(err.into()).context(uri.to_string())),
644        }
645    }
646}
647
648#[cfg(test)]
649mod test {
650    use rama_http_types::StatusCode;
651
652    use super::*;
653    use crate::{
654        IntoResponse,
655        layer::{
656            required_header::AddRequiredRequestHeadersLayer,
657            retry::{ManagedPolicy, RetryLayer},
658            trace::TraceLayer,
659        },
660    };
661    use rama_core::{
662        layer::{Layer, MapResultLayer},
663        service::{BoxService, service_fn},
664    };
665    use rama_utils::backoff::ExponentialBackoff;
666    use std::convert::Infallible;
667
668    async fn fake_client_fn<S, Body>(
669        _ctx: Context<S>,
670        request: Request<Body>,
671    ) -> Result<Response, Infallible>
672    where
673        S: Clone + Send + Sync + 'static,
674        Body: crate::dep::http_body::Body<Data: Send + 'static, Error: Send + 'static>
675            + Send
676            + 'static,
677    {
678        let ua = request.headers().get(crate::header::USER_AGENT).unwrap();
679        assert_eq!(
680            ua.to_str().unwrap(),
681            format!("{}/{}", rama_utils::info::NAME, rama_utils::info::VERSION)
682        );
683
684        Ok(StatusCode::OK.into_response())
685    }
686
687    fn map_internal_client_error<E, Body>(
688        result: Result<Response<Body>, E>,
689    ) -> Result<Response, rama_core::error::BoxError>
690    where
691        E: Into<rama_core::error::BoxError>,
692        Body: crate::dep::http_body::Body<Data = bytes::Bytes, Error: Into<BoxError>>
693            + Send
694            + Sync
695            + 'static,
696    {
697        match result {
698            Ok(response) => Ok(response.map(crate::Body::new)),
699            Err(err) => Err(err.into()),
700        }
701    }
702
703    type OpaqueError = rama_core::error::BoxError;
704    type HttpClient<S> = BoxService<S, Request, Response, OpaqueError>;
705
706    fn client<S: Clone + Send + Sync + 'static>() -> HttpClient<S> {
707        let builder = (
708            MapResultLayer::new(map_internal_client_error),
709            TraceLayer::new_for_http(),
710        );
711
712        #[cfg(feature = "compression")]
713        let builder = (
714            builder,
715            crate::layer::decompression::DecompressionLayer::new(),
716        );
717
718        (
719            builder,
720            RetryLayer::new(ManagedPolicy::default().with_backoff(ExponentialBackoff::default())),
721            AddRequiredRequestHeadersLayer::default(),
722        )
723            .into_layer(service_fn(fake_client_fn))
724            .boxed()
725    }
726
727    #[tokio::test]
728    async fn test_client_happy_path() {
729        let response = client()
730            .get("http://127.0.0.1:8080")
731            .send(Context::default())
732            .await
733            .unwrap();
734        assert_eq!(response.status(), StatusCode::OK);
735    }
736}