Skip to main content

ringline_http/
request.rs

1//! Request builder for ergonomic HTTP request construction.
2
3use crate::body::Body;
4use crate::client::HttpClient;
5use crate::error::HttpError;
6use crate::response::Response;
7use crate::streaming::StreamingResponse;
8
9/// Builder for an HTTP request.
10pub struct RequestBuilder<'a> {
11    client: &'a mut HttpClient,
12    method: String,
13    path: String,
14    headers: Vec<(String, String)>,
15    body: Body,
16}
17
18impl<'a> RequestBuilder<'a> {
19    pub(crate) fn new(client: &'a mut HttpClient, method: &str, path: &str) -> Self {
20        Self {
21            client,
22            method: method.to_string(),
23            path: path.to_string(),
24            headers: Vec::new(),
25            body: Body::Empty,
26        }
27    }
28
29    /// Add a header to the request.
30    pub fn header(mut self, name: &str, value: &str) -> Self {
31        self.headers.push((name.to_string(), value.to_string()));
32        self
33    }
34
35    /// Set the request body.
36    pub fn body(mut self, body: impl Into<Body>) -> Self {
37        self.body = body.into();
38        self
39    }
40
41    /// Send the request and return the response.
42    pub async fn send(mut self) -> Result<Response, HttpError> {
43        self.inject_accept_encoding();
44
45        let extra: Vec<(&str, &str)> = self
46            .headers
47            .iter()
48            .map(|(k, v)| (k.as_str(), v.as_str()))
49            .collect();
50
51        let body_bytes = match &self.body {
52            Body::Empty => None,
53            Body::Bytes(b) => Some(b.as_ref()),
54        };
55
56        self.client
57            .send_request(&self.method, &self.path, &extra, body_bytes)
58            .await
59    }
60
61    /// Send the request and return a streaming response.
62    ///
63    /// Returns as soon as headers are received. Body chunks are yielded
64    /// incrementally via [`StreamingResponse::next_chunk()`].
65    ///
66    /// **Note:** streaming responses do not automatically decompress the body.
67    /// If the server sends a compressed response, chunks will contain raw
68    /// compressed bytes. Buffer and decompress manually if needed.
69    pub async fn send_streaming(mut self) -> Result<StreamingResponse<'a>, HttpError> {
70        self.inject_accept_encoding();
71
72        let extra: Vec<(&str, &str)> = self
73            .headers
74            .iter()
75            .map(|(k, v)| (k.as_str(), v.as_str()))
76            .collect();
77
78        let body_bytes = match &self.body {
79            Body::Empty => None,
80            Body::Bytes(b) => Some(b.as_ref()),
81        };
82
83        self.client
84            .send_request_streaming(&self.method, &self.path, &extra, body_bytes)
85            .await
86    }
87
88    /// Inject `Accept-Encoding` header if compression features are enabled
89    /// and the caller has not already set one.
90    fn inject_accept_encoding(&mut self) {
91        let has_ae = self
92            .headers
93            .iter()
94            .any(|(name, _)| name.eq_ignore_ascii_case("accept-encoding"));
95        if !has_ae && let Some(ae) = crate::compress::accept_encoding_value() {
96            self.headers
97                .push(("accept-encoding".to_string(), ae.to_string()));
98        }
99    }
100}