Skip to main content

rama_http/service/client/
ext.rs

1use crate::{Method, Request, Response};
2use rama_core::error::BoxErrorExt as _;
3use rama_core::{
4    Service,
5    error::{BoxError, ErrorExt},
6    extensions::{Extension, Extensions, ExtensionsRef},
7};
8use rama_http_headers::authorization::Credentials;
9use rama_http_types::StreamingBody;
10use rama_net::uri::Uri;
11
12/// Extends an Http Client with high level features,
13/// to facilitate the creation and sending of http requests,
14/// in a more ergonomic way.
15pub trait HttpClientExt: private::HttpClientExtSealed + Sized + Send + Sync + 'static {
16    /// The response type returned by the `execute` method.
17    type ExecuteResponse;
18    /// The error type returned by the `execute` method.
19    type ExecuteError;
20
21    /// Convenience method to make a `GET` request to a URL.
22    ///
23    /// # Errors
24    ///
25    /// This method fails whenever the supplied [`Url`] cannot be parsed.
26    ///
27    /// [`Url`]: rama_net::uri::Uri
28    fn get(&self, url: impl IntoUrl) -> RequestBuilder<'_, Self, Self::ExecuteResponse>;
29
30    /// Convenience method to make a `POST` request to a URL.
31    ///
32    /// # Errors
33    ///
34    /// This method fails whenever the supplied [`Url`] cannot be parsed.
35    ///
36    /// [`Url`]: rama_net::uri::Uri
37    fn post(&self, url: impl IntoUrl) -> RequestBuilder<'_, Self, Self::ExecuteResponse>;
38
39    /// Convenience method to make a `PUT` request to a URL.
40    ///
41    /// # Errors
42    ///
43    /// This method fails whenever the supplied [`Url`] cannot be parsed.
44    ///
45    /// [`Url`]: rama_net::uri::Uri
46    fn put(&self, url: impl IntoUrl) -> RequestBuilder<'_, Self, Self::ExecuteResponse>;
47
48    /// Convenience method to make a `PATCH` request to a URL.
49    ///
50    /// # Errors
51    ///
52    /// This method fails whenever the supplied [`Url`] cannot be parsed.
53    ///
54    /// [`Url`]: rama_net::uri::Uri
55    fn patch(&self, url: impl IntoUrl) -> RequestBuilder<'_, Self, Self::ExecuteResponse>;
56
57    /// Convenience method to make a `DELETE` request to a URL.
58    ///
59    /// # Errors
60    ///
61    /// This method fails whenever the supplied [`Url`] cannot be parsed.
62    ///
63    /// [`Url`]: rama_net::uri::Uri
64    fn delete(&self, url: impl IntoUrl) -> RequestBuilder<'_, Self, Self::ExecuteResponse>;
65
66    /// Convenience method to make a `HEAD` request to a URL.
67    ///
68    /// # Errors
69    ///
70    /// This method fails whenever the supplied `Url` cannot be parsed.
71    fn head(&self, url: impl IntoUrl) -> RequestBuilder<'_, Self, Self::ExecuteResponse>;
72
73    /// Convenience method to make a `CONNECT` request to a URL.
74    ///
75    /// # Errors
76    ///
77    /// This method fails whenever the supplied `Url` cannot be parsed.
78    fn connect(&self, url: impl IntoUrl) -> RequestBuilder<'_, Self, Self::ExecuteResponse>;
79
80    /// Start building a [`Request`] with the [`Method`] and [`Url`].
81    ///
82    /// Returns a [`RequestBuilder`], which will allow setting headers and
83    /// the request body before sending.
84    ///
85    /// [`Request`]: crate::Request
86    /// [`Method`]: crate::Method
87    /// [`Url`]: rama_net::uri::Uri
88    ///
89    /// # Errors
90    ///
91    /// This method fails whenever the supplied [`Url`] cannot be parsed.
92    fn request(
93        &self,
94        method: Method,
95        url: impl IntoUrl,
96    ) -> RequestBuilder<'_, Self, Self::ExecuteResponse>;
97
98    /// Start building a [`Request`], using the given [`Request`].
99    ///
100    /// Returns a [`RequestBuilder`], which will allow setting headers and
101    /// the request body before sending.
102    fn build_from_request<Body: Into<crate::Body>>(
103        &self,
104        request: Request<Body>,
105    ) -> RequestBuilder<'_, Self, Self::ExecuteResponse>;
106
107    /// Executes a `Request`.
108    ///
109    /// # Errors
110    ///
111    /// This method fails if there was an error while sending request.
112    fn execute(
113        &self,
114
115        request: Request,
116    ) -> impl Future<Output = Result<Self::ExecuteResponse, Self::ExecuteError>>;
117}
118
119impl<S, Body> HttpClientExt for S
120where
121    S: Service<Request, Output = Response<Body>, Error: Into<BoxError>>,
122{
123    type ExecuteResponse = Response<Body>;
124    type ExecuteError = S::Error;
125
126    fn get(&self, url: impl IntoUrl) -> RequestBuilder<'_, Self, Self::ExecuteResponse> {
127        self.request(Method::GET, url)
128    }
129
130    fn post(&self, url: impl IntoUrl) -> RequestBuilder<'_, Self, Self::ExecuteResponse> {
131        self.request(Method::POST, url)
132    }
133
134    fn put(&self, url: impl IntoUrl) -> RequestBuilder<'_, Self, Self::ExecuteResponse> {
135        self.request(Method::PUT, url)
136    }
137
138    fn patch(&self, url: impl IntoUrl) -> RequestBuilder<'_, Self, Self::ExecuteResponse> {
139        self.request(Method::PATCH, url)
140    }
141
142    fn delete(&self, url: impl IntoUrl) -> RequestBuilder<'_, Self, Self::ExecuteResponse> {
143        self.request(Method::DELETE, url)
144    }
145
146    fn head(&self, url: impl IntoUrl) -> RequestBuilder<'_, Self, Self::ExecuteResponse> {
147        self.request(Method::HEAD, url)
148    }
149
150    fn connect(&self, url: impl IntoUrl) -> RequestBuilder<'_, Self, Self::ExecuteResponse> {
151        self.request(Method::CONNECT, url)
152    }
153
154    fn request(
155        &self,
156        method: Method,
157        url: impl IntoUrl,
158    ) -> RequestBuilder<'_, Self, Self::ExecuteResponse> {
159        let uri = match url.into_url() {
160            Ok(uri) => uri,
161            Err(err) => {
162                return RequestBuilder {
163                    http_client_service: self,
164                    state: RequestBuilderState::Error(err),
165                    _phantom: std::marker::PhantomData,
166                };
167            }
168        };
169
170        let builder = crate::request::Builder::new().method(method).uri(uri);
171
172        RequestBuilder {
173            http_client_service: self,
174            state: RequestBuilderState::PreBody(builder),
175            _phantom: std::marker::PhantomData,
176        }
177    }
178
179    fn build_from_request<RequestBody: Into<crate::Body>>(
180        &self,
181        request: Request<RequestBody>,
182    ) -> RequestBuilder<'_, Self, Self::ExecuteResponse> {
183        RequestBuilder {
184            http_client_service: self,
185            state: RequestBuilderState::PostBody(request.map(Into::into)),
186            _phantom: std::marker::PhantomData,
187        }
188    }
189
190    fn execute(
191        &self,
192
193        request: Request,
194    ) -> impl Future<Output = Result<Self::ExecuteResponse, Self::ExecuteError>> {
195        Service::serve(self, request)
196    }
197}
198
199/// A trait to try to convert some type into a [`Url`].
200///
201/// This trait is "sealed", such that only types within rama can implement it.
202///
203/// [`Url`]: rama_net::uri::Uri
204pub trait IntoUrl: private::IntoUrlSealed {}
205
206impl IntoUrl for Uri {}
207impl IntoUrl for &str {}
208impl IntoUrl for String {}
209impl IntoUrl for &String {}
210
211/// A trait to try to convert some type into a [`HeaderName`].
212///
213/// This trait is "sealed", such that only types within rama can implement it.
214///
215/// [`HeaderName`]: crate::HeaderName
216pub trait IntoHeaderName: private::IntoHeaderNameSealed {}
217
218impl IntoHeaderName for crate::HeaderName {}
219impl IntoHeaderName for Option<crate::HeaderName> {}
220impl IntoHeaderName for &str {}
221impl IntoHeaderName for String {}
222impl IntoHeaderName for &String {}
223impl IntoHeaderName for &[u8] {}
224
225/// A trait to try to convert some type into a [`HeaderValue`].
226///
227/// This trait is "sealed", such that only types within rama can implement it.
228///
229/// [`HeaderValue`]: crate::HeaderValue
230pub trait IntoHeaderValue: private::IntoHeaderValueSealed {}
231
232impl IntoHeaderValue for crate::HeaderValue {}
233impl IntoHeaderValue for &str {}
234impl IntoHeaderValue for String {}
235impl IntoHeaderValue for &String {}
236impl IntoHeaderValue for &[u8] {}
237
238mod private {
239
240    use rama_http_types::HeaderName;
241    use rama_net::Protocol;
242
243    use super::*;
244
245    pub trait IntoUrlSealed {
246        fn into_url(self) -> Result<Uri, BoxError>;
247    }
248
249    impl IntoUrlSealed for Uri {
250        fn into_url(self) -> Result<Uri, BoxError> {
251            let protocol: Option<Protocol> = self.scheme().cloned();
252            match protocol {
253                Some(protocol) => {
254                    if protocol.is_http() || protocol.is_ws() {
255                        Ok(self)
256                    } else {
257                        Err(BoxError::from_static_str("unsupported protocol")
258                            .context_field("protocol", protocol))
259                    }
260                }
261                None => Err(BoxError::from_static_str("Missing scheme in URI")),
262            }
263        }
264    }
265
266    impl IntoUrlSealed for &str {
267        fn into_url(self) -> Result<Uri, BoxError> {
268            match self.parse::<Uri>() {
269                Ok(uri) => uri.into_url(),
270                Err(_) => {
271                    Err(BoxError::from_static_str("invalid url").context_str_field("raw_str", self))
272                }
273            }
274        }
275    }
276
277    impl IntoUrlSealed for String {
278        #[inline(always)]
279        fn into_url(self) -> Result<Uri, BoxError> {
280            self.as_str().into_url()
281        }
282    }
283
284    impl IntoUrlSealed for &String {
285        #[inline(always)]
286        fn into_url(self) -> Result<Uri, BoxError> {
287            self.as_str().into_url()
288        }
289    }
290
291    pub trait IntoHeaderNameSealed {
292        fn into_header_name(self) -> Result<crate::HeaderName, BoxError>;
293    }
294
295    impl IntoHeaderNameSealed for HeaderName {
296        fn into_header_name(self) -> Result<crate::HeaderName, BoxError> {
297            Ok(self)
298        }
299    }
300
301    impl IntoHeaderNameSealed for Option<HeaderName> {
302        fn into_header_name(self) -> Result<crate::HeaderName, BoxError> {
303            match self {
304                Some(name) => Ok(name),
305                None => Err(BoxError::from_static_str("Header name is required")),
306            }
307        }
308    }
309
310    impl IntoHeaderNameSealed for &str {
311        fn into_header_name(self) -> Result<crate::HeaderName, BoxError> {
312            let name = self.parse::<crate::HeaderName>()?;
313            Ok(name)
314        }
315    }
316
317    impl IntoHeaderNameSealed for String {
318        #[inline(always)]
319        fn into_header_name(self) -> Result<crate::HeaderName, BoxError> {
320            self.as_str().into_header_name()
321        }
322    }
323
324    impl IntoHeaderNameSealed for &String {
325        #[inline(always)]
326        fn into_header_name(self) -> Result<crate::HeaderName, BoxError> {
327            self.as_str().into_header_name()
328        }
329    }
330
331    impl IntoHeaderNameSealed for &[u8] {
332        fn into_header_name(self) -> Result<crate::HeaderName, BoxError> {
333            let name = crate::HeaderName::from_bytes(self)?;
334            Ok(name)
335        }
336    }
337
338    pub trait IntoHeaderValueSealed {
339        fn into_header_value(self) -> Result<crate::HeaderValue, BoxError>;
340    }
341
342    impl IntoHeaderValueSealed for crate::HeaderValue {
343        fn into_header_value(self) -> Result<crate::HeaderValue, BoxError> {
344            Ok(self)
345        }
346    }
347
348    impl IntoHeaderValueSealed for &str {
349        fn into_header_value(self) -> Result<crate::HeaderValue, BoxError> {
350            let value = self.parse::<crate::HeaderValue>()?;
351            Ok(value)
352        }
353    }
354
355    impl IntoHeaderValueSealed for String {
356        fn into_header_value(self) -> Result<crate::HeaderValue, BoxError> {
357            self.as_str().into_header_value()
358        }
359    }
360
361    impl IntoHeaderValueSealed for &String {
362        #[inline(always)]
363        fn into_header_value(self) -> Result<crate::HeaderValue, BoxError> {
364            self.as_str().into_header_value()
365        }
366    }
367
368    impl IntoHeaderValueSealed for &[u8] {
369        fn into_header_value(self) -> Result<crate::HeaderValue, BoxError> {
370            let value = crate::HeaderValue::from_bytes(self)?;
371            Ok(value)
372        }
373    }
374
375    pub trait HttpClientExtSealed {}
376
377    impl<S, Body> HttpClientExtSealed for S where
378        S: Service<Request, Output = Response<Body>, Error: Into<BoxError>>
379    {
380    }
381}
382
383/// A builder to construct the properties of a [`Request`].
384///
385/// Constructed using [`HttpClientExt`].
386pub struct RequestBuilder<'a, S, Response> {
387    http_client_service: &'a S,
388    state: RequestBuilderState,
389    _phantom: std::marker::PhantomData<fn(Response) -> ()>,
390}
391
392impl<S, Response> std::fmt::Debug for RequestBuilder<'_, S, Response>
393where
394    S: std::fmt::Debug,
395{
396    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
397        f.debug_struct("RequestBuilder")
398            .field("http_client_service", &self.http_client_service)
399            .field("state", &self.state)
400            .finish()
401    }
402}
403
404#[derive(Debug)]
405enum RequestBuilderState {
406    PreBody(crate::request::Builder),
407    PostBody(crate::Request),
408    Error(BoxError),
409}
410
411impl<S, Body> RequestBuilder<'_, S, Response<Body>>
412where
413    S: Service<Request, Output = Response<Body>, Error: Into<BoxError>>,
414{
415    /// Add a `Header` to this [`Request`].
416    #[must_use]
417    pub fn header<K, V>(mut self, key: K, value: V) -> Self
418    where
419        K: IntoHeaderName,
420        V: IntoHeaderValue,
421    {
422        match self.state {
423            RequestBuilderState::PreBody(builder) => {
424                let key = match key.into_header_name() {
425                    Ok(key) => key,
426                    Err(err) => {
427                        self.state = RequestBuilderState::Error(err);
428                        return self;
429                    }
430                };
431                let value = match value.into_header_value() {
432                    Ok(value) => value,
433                    Err(err) => {
434                        self.state = RequestBuilderState::Error(err);
435                        return self;
436                    }
437                };
438                self.state = RequestBuilderState::PreBody(builder.header(key, value));
439                self
440            }
441            RequestBuilderState::PostBody(mut request) => {
442                let key = match key.into_header_name() {
443                    Ok(key) => key,
444                    Err(err) => {
445                        self.state = RequestBuilderState::Error(err);
446                        return self;
447                    }
448                };
449                let value = match value.into_header_value() {
450                    Ok(value) => value,
451                    Err(err) => {
452                        self.state = RequestBuilderState::Error(err);
453                        return self;
454                    }
455                };
456                request.headers_mut().append(key, value);
457                self.state = RequestBuilderState::PostBody(request);
458                self
459            }
460            RequestBuilderState::Error(err) => {
461                self.state = RequestBuilderState::Error(err);
462                self
463            }
464        }
465    }
466
467    /// Add a typed [`HeaderEncode`] to this [`Request`].
468    ///
469    /// [`HeaderEncode`]: crate::headers::HeaderEncode
470    #[must_use]
471    pub fn typed_header<H>(self, header: H) -> Self
472    where
473        H: crate::headers::HeaderEncode,
474    {
475        if let Some(value) = header.encode_to_value() {
476            self.header(H::name().clone(), value)
477        } else {
478            self
479        }
480    }
481
482    /// Add all `Headers` from the [`HeaderMap`] to this [`Request`].
483    ///
484    /// [`HeaderMap`]: crate::HeaderMap
485    #[must_use]
486    pub fn headers(mut self, headers: crate::HeaderMap) -> Self {
487        for (key, value) in headers.into_iter() {
488            self = self.header(key, value);
489        }
490        self
491    }
492
493    /// Overwrite a `Header` to this [`Request`].
494    #[must_use]
495    pub fn overwrite_header<K, V>(mut self, key: K, value: V) -> Self
496    where
497        K: IntoHeaderName,
498        V: IntoHeaderValue,
499    {
500        match self.state {
501            RequestBuilderState::PreBody(mut builder) => {
502                // None in case builder has errors
503                if let Some(headers) = builder.headers_mut() {
504                    let key = match key.into_header_name() {
505                        Ok(key) => key,
506                        Err(err) => {
507                            self.state = RequestBuilderState::Error(err);
508                            return self;
509                        }
510                    };
511                    let value = match value.into_header_value() {
512                        Ok(value) => value,
513                        Err(err) => {
514                            self.state = RequestBuilderState::Error(err);
515                            return self;
516                        }
517                    };
518                    _ = headers.insert(key, value);
519                }
520
521                self.state = RequestBuilderState::PreBody(builder);
522                self
523            }
524            RequestBuilderState::PostBody(mut request) => {
525                let key = match key.into_header_name() {
526                    Ok(key) => key,
527                    Err(err) => {
528                        self.state = RequestBuilderState::Error(err);
529                        return self;
530                    }
531                };
532                let value = match value.into_header_value() {
533                    Ok(value) => value,
534                    Err(err) => {
535                        self.state = RequestBuilderState::Error(err);
536                        return self;
537                    }
538                };
539                _ = request.headers_mut().insert(key, value);
540                self.state = RequestBuilderState::PostBody(request);
541                self
542            }
543            RequestBuilderState::Error(err) => {
544                self.state = RequestBuilderState::Error(err);
545                self
546            }
547        }
548    }
549
550    /// Overwrite a typed [`HeaderEncode`] to this [`Request`].
551    ///
552    /// [`HeaderEncode`]: crate::headers::HeaderEncode
553    #[must_use]
554    pub fn overwrite_typed_header<H>(self, header: H) -> Self
555    where
556        H: crate::headers::HeaderEncode,
557    {
558        if let Some(value) = header.encode_to_value() {
559            self.overwrite_header(H::name().clone(), value)
560        } else {
561            self
562        }
563    }
564
565    /// Enable HTTP authentication.
566    #[must_use]
567    pub fn auth(self, credentials: impl Credentials) -> Self {
568        let header = crate::headers::Authorization::new(credentials);
569        self.typed_header(header)
570    }
571
572    /// Adds an extension to this builder
573    #[must_use]
574    pub fn extension<T>(mut self, extension: T) -> Self
575    where
576        T: Extension,
577    {
578        match self.state {
579            RequestBuilderState::PreBody(builder) => {
580                let builder = builder.extension(extension);
581                self.state = RequestBuilderState::PreBody(builder);
582                self
583            }
584            RequestBuilderState::PostBody(request) => {
585                request.extensions().insert(extension);
586                self.state = RequestBuilderState::PostBody(request);
587                self
588            }
589            state @ RequestBuilderState::Error(_) => {
590                self.state = state;
591                self
592            }
593        }
594    }
595
596    /// Get access to the underlying [`Extensions`]
597    ///
598    /// This function will return None if [`Extensions`] are not available,
599    /// or if this builder is in an error state
600    pub fn extensions(&self) -> Option<&Extensions> {
601        match &self.state {
602            RequestBuilderState::PreBody(builder) => builder.extensions(),
603            RequestBuilderState::PostBody(request) => Some(request.extensions()),
604            RequestBuilderState::Error(_) => None,
605        }
606    }
607
608    /// Set the [`Request`]'s [`Body`].
609    ///
610    /// Re-syncs `Content-Length` with the new body: when the body has a
611    /// known exact size, `Content-Length` is set to that value, replacing
612    /// any prior value. When the size is unknown, any pre-set
613    /// `Content-Length` is removed so the request doesn't ship with stale
614    /// framing.
615    ///
616    /// [`Body`]: crate::Body
617    #[must_use]
618    pub fn body<T>(mut self, body: T) -> Self
619    where
620        T: TryInto<crate::Body, Error: Into<BoxError>>,
621    {
622        self.state = match self.state {
623            RequestBuilderState::PreBody(mut builder) => match body.try_into() {
624                Ok(body) => {
625                    sync_content_length_pre_body(&mut builder, &body);
626                    match builder.body(body) {
627                        Ok(req) => RequestBuilderState::PostBody(req),
628                        Err(err) => RequestBuilderState::Error(BoxError::from(err)),
629                    }
630                }
631                Err(err) => RequestBuilderState::Error(err.into()),
632            },
633            RequestBuilderState::PostBody(mut req) => match body.try_into() {
634                Ok(body) => {
635                    sync_content_length_post_body(&mut req, &body);
636                    *req.body_mut() = body;
637                    RequestBuilderState::PostBody(req)
638                }
639                Err(err) => RequestBuilderState::Error(BoxError::from(err.into())),
640            },
641            RequestBuilderState::Error(err) => RequestBuilderState::Error(err),
642        };
643        self
644    }
645
646    /// Set the given value as a URL-Encoded Form [`Body`] in the [`Request`].
647    ///
648    /// Sets `Content-Type: application/x-www-form-urlencoded` if no
649    /// `Content-Type` header is already set. Sets `Content-Length` to the
650    /// encoded body's size, replacing any prior value.
651    ///
652    /// [`Body`]: crate::Body
653    #[must_use]
654    pub fn form<T: serde::Serialize + ?Sized>(mut self, form: &T) -> Self {
655        self.state = match self.state {
656            RequestBuilderState::PreBody(mut builder) => match serde_html_form::to_string(form) {
657                Ok(body) => {
658                    let len = body.len() as u64;
659                    let builder = match builder.headers_mut() {
660                        Some(headers) => {
661                            if !headers.contains_key(crate::header::CONTENT_TYPE) {
662                                headers.insert(
663                                    crate::header::CONTENT_TYPE,
664                                    crate::HeaderValue::from_static(
665                                        "application/x-www-form-urlencoded",
666                                    ),
667                                );
668                            }
669                            headers.insert(
670                                crate::header::CONTENT_LENGTH,
671                                crate::HeaderValue::from(len),
672                            );
673                            builder
674                        }
675                        None => builder
676                            .header(
677                                crate::header::CONTENT_TYPE,
678                                crate::HeaderValue::from_static(
679                                    "application/x-www-form-urlencoded",
680                                ),
681                            )
682                            .header(crate::header::CONTENT_LENGTH, crate::HeaderValue::from(len)),
683                    };
684                    match builder.body(body.into()) {
685                        Ok(req) => RequestBuilderState::PostBody(req),
686                        Err(err) => RequestBuilderState::Error(BoxError::from(err)),
687                    }
688                }
689                Err(err) => RequestBuilderState::Error(BoxError::from(err)),
690            },
691            RequestBuilderState::PostBody(mut req) => match serde_html_form::to_string(form) {
692                Ok(body) => {
693                    let len = body.len() as u64;
694                    if !req.headers().contains_key(crate::header::CONTENT_TYPE) {
695                        req.headers_mut().insert(
696                            crate::header::CONTENT_TYPE,
697                            crate::HeaderValue::from_static("application/x-www-form-urlencoded"),
698                        );
699                    }
700                    req.headers_mut()
701                        .insert(crate::header::CONTENT_LENGTH, crate::HeaderValue::from(len));
702                    *req.body_mut() = body.into();
703                    RequestBuilderState::PostBody(req)
704                }
705                Err(err) => RequestBuilderState::Error(BoxError::from(err)),
706            },
707            RequestBuilderState::Error(err) => RequestBuilderState::Error(err),
708        };
709        self
710    }
711
712    /// Set the given value as a JSON [`Body`] in the [`Request`].
713    ///
714    /// Sets `Content-Type: application/json` if no `Content-Type` header is
715    /// already set. Sets `Content-Length` to the encoded body's size,
716    /// replacing any prior value.
717    ///
718    /// [`Body`]: crate::Body
719    #[must_use]
720    pub fn json<T: serde::Serialize + ?Sized>(mut self, json: &T) -> Self {
721        self.state = match self.state {
722            RequestBuilderState::PreBody(mut builder) => match serde_json::to_vec(json) {
723                Ok(body) => {
724                    let len = body.len() as u64;
725                    let builder = match builder.headers_mut() {
726                        Some(headers) => {
727                            if !headers.contains_key(crate::header::CONTENT_TYPE) {
728                                headers.insert(
729                                    crate::header::CONTENT_TYPE,
730                                    crate::HeaderValue::from_static("application/json"),
731                                );
732                            }
733                            headers.insert(
734                                crate::header::CONTENT_LENGTH,
735                                crate::HeaderValue::from(len),
736                            );
737                            builder
738                        }
739                        None => builder
740                            .header(
741                                crate::header::CONTENT_TYPE,
742                                crate::HeaderValue::from_static("application/json"),
743                            )
744                            .header(crate::header::CONTENT_LENGTH, crate::HeaderValue::from(len)),
745                    };
746                    match builder.body(body.into()) {
747                        Ok(req) => RequestBuilderState::PostBody(req),
748                        Err(err) => RequestBuilderState::Error(BoxError::from(err)),
749                    }
750                }
751                Err(err) => RequestBuilderState::Error(BoxError::from(err)),
752            },
753            RequestBuilderState::PostBody(mut req) => match serde_json::to_vec(json) {
754                Ok(body) => {
755                    let len = body.len() as u64;
756                    if !req.headers().contains_key(crate::header::CONTENT_TYPE) {
757                        req.headers_mut().insert(
758                            crate::header::CONTENT_TYPE,
759                            crate::HeaderValue::from_static("application/json"),
760                        );
761                    }
762                    req.headers_mut()
763                        .insert(crate::header::CONTENT_LENGTH, crate::HeaderValue::from(len));
764                    *req.body_mut() = body.into();
765                    RequestBuilderState::PostBody(req)
766                }
767                Err(err) => RequestBuilderState::Error(BoxError::from(err)),
768            },
769            RequestBuilderState::Error(err) => RequestBuilderState::Error(err),
770        };
771        self
772    }
773
774    /// Set a [`multipart::Form`](super::multipart::Form) as the body of this
775    /// [`Request`].
776    ///
777    /// Sets the `Content-Type` header to
778    /// `multipart/form-data; boundary={boundary}` and, when every part has a
779    /// known size, the `Content-Length` header. An existing `Content-Type`
780    /// header is overwritten.
781    #[cfg(feature = "multipart")]
782    #[must_use]
783    pub fn multipart(mut self, form: super::multipart::Form) -> Self {
784        let content_type = form.content_type();
785        let content_length = form.content_length();
786        let body = form.into_body();
787        self.state = match self.state {
788            RequestBuilderState::PreBody(mut builder) => {
789                if let Some(headers) = builder.headers_mut() {
790                    headers.insert(crate::header::CONTENT_TYPE, content_type);
791                    sync_content_length_in_map(headers, content_length);
792                }
793                // If `headers_mut` is None the builder is in an error
794                // state; `builder.body(...)` will surface that error.
795                match builder.body(body) {
796                    Ok(req) => RequestBuilderState::PostBody(req),
797                    Err(err) => RequestBuilderState::Error(BoxError::from(err)),
798                }
799            }
800            RequestBuilderState::PostBody(mut req) => {
801                let headers = req.headers_mut();
802                headers.insert(crate::header::CONTENT_TYPE, content_type);
803                sync_content_length_in_map(headers, content_length);
804                *req.body_mut() = body;
805                RequestBuilderState::PostBody(req)
806            }
807            RequestBuilderState::Error(err) => RequestBuilderState::Error(err),
808        };
809        self
810    }
811
812    /// Set the given bytes as an `application/octet-stream` [`Body`] in the [`Request`].
813    ///
814    /// Sets `Content-Type: application/octet-stream` if no `Content-Type`
815    /// header is already set on the request. Sets `Content-Length` to the
816    /// payload's size, replacing any prior value.
817    ///
818    /// [`Body`]: crate::Body
819    #[must_use]
820    pub fn octet_stream<T: Into<rama_core::bytes::Bytes>>(mut self, bytes: T) -> Self {
821        let bytes = bytes.into();
822        let len = bytes.len() as u64;
823        self.state = match self.state {
824            RequestBuilderState::PreBody(mut builder) => {
825                let builder = match builder.headers_mut() {
826                    Some(headers) => {
827                        if !headers.contains_key(crate::header::CONTENT_TYPE) {
828                            headers.insert(
829                                crate::header::CONTENT_TYPE,
830                                crate::HeaderValue::from_static("application/octet-stream"),
831                            );
832                        }
833                        headers
834                            .insert(crate::header::CONTENT_LENGTH, crate::HeaderValue::from(len));
835                        builder
836                    }
837                    None => builder
838                        .header(
839                            crate::header::CONTENT_TYPE,
840                            crate::HeaderValue::from_static("application/octet-stream"),
841                        )
842                        .header(crate::header::CONTENT_LENGTH, crate::HeaderValue::from(len)),
843                };
844                match builder.body(bytes.into()) {
845                    Ok(req) => RequestBuilderState::PostBody(req),
846                    Err(err) => RequestBuilderState::Error(BoxError::from(err)),
847                }
848            }
849            RequestBuilderState::PostBody(mut req) => {
850                if !req.headers().contains_key(crate::header::CONTENT_TYPE) {
851                    req.headers_mut().insert(
852                        crate::header::CONTENT_TYPE,
853                        crate::HeaderValue::from_static("application/octet-stream"),
854                    );
855                }
856                req.headers_mut()
857                    .insert(crate::header::CONTENT_LENGTH, crate::HeaderValue::from(len));
858                *req.body_mut() = bytes.into();
859                RequestBuilderState::PostBody(req)
860            }
861            RequestBuilderState::Error(err) => RequestBuilderState::Error(err),
862        };
863        self
864    }
865
866    /// Set the http [`Version`] of this [`Request`].
867    ///
868    /// [`Version`]: crate::Version
869    #[must_use]
870    pub fn version(mut self, version: crate::Version) -> Self {
871        match self.state {
872            RequestBuilderState::PreBody(builder) => {
873                self.state = RequestBuilderState::PreBody(builder.version(version));
874                self
875            }
876            RequestBuilderState::PostBody(mut request) => {
877                *request.version_mut() = version;
878                self.state = RequestBuilderState::PostBody(request);
879                self
880            }
881            RequestBuilderState::Error(err) => {
882                self.state = RequestBuilderState::Error(err);
883                self
884            }
885        }
886    }
887
888    /// Constructs the [`Request`].
889    ///
890    /// # Errors
891    ///
892    /// This method fails if there was an error while constructing the [`Request`].
893    pub async fn try_into_request(self) -> Result<Request, BoxError> {
894        Ok(match self.state {
895            RequestBuilderState::PreBody(builder) => builder.body(crate::Body::empty())?,
896            RequestBuilderState::PostBody(request) => request,
897            RequestBuilderState::Error(err) => return Err(err),
898        })
899    }
900
901    /// Constructs the [`Request`] and sends it to the target [`Uri`], returning a future [`Response`].
902    ///
903    /// # Errors
904    ///
905    /// This method fails if there was an error while sending [`Request`].
906    pub async fn send(self) -> Result<Response<Body>, BoxError> {
907        let request = match self.state {
908            RequestBuilderState::PreBody(builder) => builder.body(crate::Body::empty())?,
909            RequestBuilderState::PostBody(request) => request,
910            RequestBuilderState::Error(err) => return Err(err),
911        };
912
913        let uri = request.uri().clone();
914        match self.http_client_service.serve(request).await {
915            Ok(response) => Ok(response),
916            Err(err) => Err(err.into().context(uri)),
917        }
918    }
919}
920
921/// Synchronise `Content-Length` with the new body on a pre-body request
922/// builder.
923///
924/// If the body has a known exact size, set `Content-Length` to that value
925/// (replacing any prior). If the size is unknown, remove any pre-set
926/// `Content-Length` so the request doesn't ship with stale framing.
927fn sync_content_length_pre_body(builder: &mut crate::request::Builder, body: &crate::Body) {
928    let Some(headers) = builder.headers_mut() else {
929        return;
930    };
931    let exact = body.size_hint().exact();
932    sync_content_length_in_map(headers, exact);
933}
934
935/// Synchronise `Content-Length` with the new body on a request that has
936/// already entered the post-body state.
937fn sync_content_length_post_body<B>(req: &mut crate::Request<B>, body: &crate::Body) {
938    let exact = body.size_hint().exact();
939    sync_content_length_in_map(req.headers_mut(), exact);
940}
941
942fn sync_content_length_in_map(headers: &mut crate::HeaderMap, exact: Option<u64>) {
943    match exact {
944        Some(len) => {
945            headers.insert(crate::header::CONTENT_LENGTH, crate::HeaderValue::from(len));
946        }
947        None => {
948            headers.remove(crate::header::CONTENT_LENGTH);
949        }
950    }
951}
952
953#[cfg(test)]
954mod test {
955    use rama_http_types::StatusCode;
956
957    use super::*;
958    use crate::{
959        StreamingBody,
960        layer::{
961            required_header::AddRequiredRequestHeadersLayer,
962            retry::{ManagedPolicy, RetryLayer},
963            trace::TraceLayer,
964        },
965        service::web::response::IntoResponse,
966    };
967    use rama_core::{
968        layer::{Layer, MapResultLayer},
969        service::{BoxService, service_fn},
970    };
971    use rama_utils::backoff::ExponentialBackoff;
972    use std::convert::Infallible;
973
974    async fn fake_client_fn<Body>(request: Request<Body>) -> Result<Response, Infallible>
975    where
976        Body: StreamingBody<Data: Send + 'static, Error: Send + 'static> + Send + 'static,
977    {
978        let ua = request.headers().get(crate::header::USER_AGENT).unwrap();
979        assert_eq!(
980            ua.to_str().unwrap(),
981            format!("{}/{}", rama_utils::info::NAME, rama_utils::info::VERSION)
982        );
983
984        Ok(StatusCode::OK.into_response())
985    }
986
987    fn map_internal_client_error<E, Body>(
988        result: Result<Response<Body>, E>,
989    ) -> Result<Response, rama_core::error::BoxError>
990    where
991        E: Into<rama_core::error::BoxError>,
992        Body: StreamingBody<Data = rama_core::bytes::Bytes, Error: Into<BoxError>>
993            + Send
994            + Sync
995            + 'static,
996    {
997        match result {
998            Ok(response) => Ok(response.map(crate::Body::new)),
999            Err(err) => Err(err.into()),
1000        }
1001    }
1002
1003    type HttpClient = BoxService<Request, Response, BoxError>;
1004
1005    fn client() -> HttpClient {
1006        let builder = (
1007            MapResultLayer::new(map_internal_client_error),
1008            TraceLayer::new_for_http(),
1009        );
1010
1011        #[cfg(feature = "compression")]
1012        let builder = (
1013            builder,
1014            crate::layer::decompression::DecompressionLayer::new(),
1015        );
1016
1017        (
1018            builder,
1019            RetryLayer::new(ManagedPolicy::default().with_backoff(ExponentialBackoff::default())),
1020            AddRequiredRequestHeadersLayer::default(),
1021        )
1022            .into_layer(service_fn(fake_client_fn))
1023            .boxed()
1024    }
1025
1026    #[tokio::test]
1027    async fn test_client_happy_path() {
1028        let response = client().get("http://127.0.0.1:8080").send().await.unwrap();
1029        assert_eq!(response.status(), StatusCode::OK);
1030    }
1031
1032    /// Capture the request the builder would send and return it as the
1033    /// response body so tests can inspect the outgoing headers.
1034    async fn echo_headers_client_fn(request: Request<crate::Body>) -> Result<Response, Infallible> {
1035        let mut headers_lines = Vec::new();
1036        for (name, value) in request.headers() {
1037            headers_lines.push(format!(
1038                "{}: {}",
1039                name.as_str(),
1040                value.to_str().unwrap_or("<bin>")
1041            ));
1042        }
1043        let body = headers_lines.join("\n");
1044        Ok(crate::Response::new(crate::Body::from(body)))
1045    }
1046
1047    fn echo_client() -> HttpClient {
1048        let builder = (
1049            MapResultLayer::new(map_internal_client_error),
1050            TraceLayer::new_for_http(),
1051        );
1052        (builder,)
1053            .into_layer(service_fn(echo_headers_client_fn))
1054            .boxed()
1055    }
1056
1057    async fn dump_headers(resp: Response) -> String {
1058        use crate::body::util::BodyExt as _;
1059        let bytes = resp.into_body().collect().await.unwrap().to_bytes();
1060        String::from_utf8(bytes.to_vec()).unwrap()
1061    }
1062
1063    #[tokio::test]
1064    async fn test_form_helper_sets_content_length_and_overwrites_stale() {
1065        let resp = echo_client()
1066            .post("http://x/")
1067            .header(crate::header::CONTENT_LENGTH, "9999") // stale, should be overwritten
1068            .form(&[("k", "vvv")])
1069            .send()
1070            .await
1071            .unwrap();
1072        let dump = dump_headers(resp).await;
1073        // serde_html_form encodes `[("k", "vvv")]` as `k=vvv` (5 bytes).
1074        assert!(
1075            dump.contains("content-length: 5"),
1076            "expected content-length: 5 in:\n{dump}"
1077        );
1078        assert!(
1079            dump.contains("content-type: application/x-www-form-urlencoded"),
1080            "missing CT in:\n{dump}"
1081        );
1082    }
1083
1084    #[tokio::test]
1085    async fn test_json_helper_sets_content_length_and_overwrites_stale() {
1086        let resp = echo_client()
1087            .post("http://x/")
1088            .header(crate::header::CONTENT_LENGTH, "9999")
1089            .json(&serde_json::json!({"a": 1}))
1090            .send()
1091            .await
1092            .unwrap();
1093        let dump = dump_headers(resp).await;
1094        // `{"a":1}` is 7 bytes.
1095        assert!(
1096            dump.contains("content-length: 7"),
1097            "expected content-length: 7 in:\n{dump}"
1098        );
1099        assert!(dump.contains("content-type: application/json"));
1100    }
1101
1102    #[tokio::test]
1103    async fn test_octet_stream_helper_sets_content_length_and_overwrites_stale() {
1104        let resp = echo_client()
1105            .post("http://x/")
1106            .header(crate::header::CONTENT_LENGTH, "9999")
1107            .octet_stream(b"abcd".as_slice())
1108            .send()
1109            .await
1110            .unwrap();
1111        let dump = dump_headers(resp).await;
1112        assert!(
1113            dump.contains("content-length: 4"),
1114            "expected content-length: 4 in:\n{dump}"
1115        );
1116        assert!(dump.contains("content-type: application/octet-stream"));
1117    }
1118
1119    #[tokio::test]
1120    async fn test_body_helper_clears_content_length_for_streaming_body() {
1121        // A streaming body with no exact size_hint must not ship with the
1122        // stale Content-Length the user pre-set.
1123        use rama_core::futures::stream;
1124        let stream = stream::iter([Ok::<_, BoxError>(rama_core::bytes::Bytes::from_static(
1125            b"chunk",
1126        ))]);
1127        let body = crate::Body::from_stream(stream);
1128
1129        let resp = echo_client()
1130            .post("http://x/")
1131            .header(crate::header::CONTENT_LENGTH, "9999")
1132            .body(body)
1133            .send()
1134            .await
1135            .unwrap();
1136        let dump = dump_headers(resp).await;
1137        assert!(
1138            !dump.to_ascii_lowercase().contains("content-length:"),
1139            "stale content-length leaked into:\n{dump}"
1140        );
1141    }
1142}