Skip to main content

better_fetch/
request.rs

1use std::collections::HashMap;
2use std::time::Duration;
3
4use bytes::Bytes;
5use http::{HeaderMap, Method};
6
7use crate::auth::Auth;
8use crate::client::Client;
9use crate::error::Error;
10use crate::response::Response;
11use crate::retry::RetryPolicy;
12use crate::url_build::QueryValue;
13use crate::Result;
14
15#[cfg(feature = "json")]
16use crate::json_parser::JsonParserFn;
17
18/// Fluent builder for a single HTTP request.
19pub struct RequestBuilder<'a> {
20    pub(crate) client: &'a Client,
21    pub(crate) method: Method,
22    pub(crate) path: String,
23    pub(crate) params: HashMap<String, String>,
24    pub(crate) query: HashMap<String, QueryValue>,
25    pub(crate) headers: HeaderMap,
26    pub(crate) body: Option<Bytes>,
27    pub(crate) timeout: Option<Duration>,
28    pub(crate) retry: Option<RetryPolicy>,
29    pub(crate) auth: Option<Auth>,
30    #[cfg(feature = "json")]
31    pub(crate) json_parser: Option<JsonParserFn>,
32    #[cfg(feature = "validate")]
33    pub(crate) validate_response: bool,
34}
35
36impl<'a> RequestBuilder<'a> {
37    pub fn param(mut self, key: impl Into<String>, value: impl ToString) -> Self {
38        self.params.insert(key.into(), value.to_string());
39        self
40    }
41
42    pub fn params(mut self, params: HashMap<String, String>) -> Self {
43        self.params.extend(params);
44        self
45    }
46
47    pub fn params_iter(
48        mut self,
49        params: impl IntoIterator<Item = (impl Into<String>, impl ToString)>,
50    ) -> Self {
51        for (k, v) in params {
52            self.params.insert(k.into(), v.to_string());
53        }
54        self
55    }
56
57    pub fn query(mut self, key: impl Into<String>, value: impl ToString) -> Self {
58        self.query
59            .insert(key.into(), QueryValue::Scalar(value.to_string()));
60        self
61    }
62
63    #[cfg(feature = "json")]
64    pub fn query_json<T: serde::Serialize>(
65        mut self,
66        key: impl Into<String>,
67        value: &T,
68    ) -> Result<Self> {
69        self.query
70            .insert(key.into(), QueryValue::from_serializable(value)?);
71        Ok(self)
72    }
73
74    pub fn header(mut self, key: impl AsRef<str>, value: impl AsRef<str>) -> Result<Self> {
75        let name = http::HeaderName::from_bytes(key.as_ref().as_bytes())
76            .map_err(|e| Error::Other(format!("invalid header name: {e}")))?;
77        let value = http::HeaderValue::from_str(value.as_ref())
78            .map_err(|e| Error::Other(format!("invalid header value: {e}")))?;
79        self.headers.insert(name, value);
80        Ok(self)
81    }
82
83    #[cfg(feature = "json")]
84    pub fn json<T: serde::Serialize>(mut self, body: &T) -> Result<Self> {
85        let bytes = serde_json::to_vec(body).map_err(|e| Error::Other(e.to_string()))?;
86        self.body = Some(Bytes::from(bytes));
87        if !self.headers.contains_key(http::header::CONTENT_TYPE) {
88            self.headers.insert(
89                http::header::CONTENT_TYPE,
90                http::HeaderValue::from_static("application/json"),
91            );
92        }
93        Ok(self)
94    }
95
96    pub fn body(mut self, body: impl Into<Bytes>) -> Self {
97        self.body = Some(body.into());
98        self
99    }
100
101    pub fn timeout(mut self, timeout: Duration) -> Self {
102        self.timeout = Some(timeout);
103        self
104    }
105
106    pub fn retry(mut self, policy: RetryPolicy) -> Self {
107        self.retry = Some(policy);
108        self
109    }
110
111    pub fn auth(mut self, auth: Auth) -> Self {
112        self.auth = Some(auth);
113        self
114    }
115
116    pub fn bearer_token(mut self, token: impl Into<String>) -> Self {
117        self.auth = Some(Auth::bearer(token));
118        self
119    }
120
121    /// Overrides the client's JSON parser for this request only.
122    #[cfg(feature = "json")]
123    pub fn json_parser<F>(mut self, f: F) -> Self
124    where
125        F: Fn(&Bytes) -> std::result::Result<serde_json::Value, String> + Send + Sync + 'static,
126    {
127        self.json_parser = Some(crate::json_parser::json_parser(f));
128        self
129    }
130
131    /// Overrides the client's JSON parser for this request only.
132    #[cfg(feature = "json")]
133    pub fn json_parser_fn(mut self, parser: JsonParserFn) -> Self {
134        self.json_parser = Some(parser);
135        self
136    }
137
138    pub async fn send(self) -> Result<Response> {
139        self.client.execute(self).await
140    }
141
142    #[cfg(feature = "json")]
143    #[must_use = "send the request with `.await` and handle the result"]
144    pub async fn send_json<T: serde::de::DeserializeOwned>(self) -> Result<T> {
145        self.send().await?.json::<T>().await
146    }
147
148    /// When `false`, [`send_json_validated`](Self::send_json_validated) only deserializes (no garde).
149    #[cfg(feature = "validate")]
150    pub fn validate_response(mut self, validate: bool) -> Self {
151        self.validate_response = validate;
152        self
153    }
154
155    /// `send` + [`Response::json_validated`](crate::Response::json_validated).
156    #[cfg(feature = "validate")]
157    pub async fn send_json_validated<T>(self) -> Result<T>
158    where
159        T: serde::de::DeserializeOwned + garde::Validate,
160        T::Context: Default,
161    {
162        if !self.validate_response {
163            return self.send_json().await;
164        }
165        self.send().await?.json_validated().await
166    }
167}