oxihttp_core/
request_builder.rs1use bytes::Bytes;
8use http::{HeaderMap, HeaderName, HeaderValue, Method, Uri};
9
10use crate::body::Body;
11use crate::error::OxiHttpError;
12use crate::OxiRequest;
13
14#[derive(Debug)]
36pub struct RequestBuilder {
37 method: Method,
38 uri: Uri,
39 headers: HeaderMap,
40 body: Body,
41}
42
43impl RequestBuilder {
44 pub fn new(method: Method, uri: Uri) -> Self {
46 Self {
47 method,
48 uri,
49 headers: HeaderMap::new(),
50 body: Body::empty(),
51 }
52 }
53
54 pub fn get(uri: impl AsRef<str>) -> Result<Self, OxiHttpError> {
56 let uri = Uri::try_from(uri.as_ref())?;
57 Ok(Self::new(Method::GET, uri))
58 }
59
60 pub fn post(uri: impl AsRef<str>) -> Result<Self, OxiHttpError> {
62 let uri = Uri::try_from(uri.as_ref())?;
63 Ok(Self::new(Method::POST, uri))
64 }
65
66 pub fn put(uri: impl AsRef<str>) -> Result<Self, OxiHttpError> {
68 let uri = Uri::try_from(uri.as_ref())?;
69 Ok(Self::new(Method::PUT, uri))
70 }
71
72 pub fn delete(uri: impl AsRef<str>) -> Result<Self, OxiHttpError> {
74 let uri = Uri::try_from(uri.as_ref())?;
75 Ok(Self::new(Method::DELETE, uri))
76 }
77
78 pub fn patch(uri: impl AsRef<str>) -> Result<Self, OxiHttpError> {
80 let uri = Uri::try_from(uri.as_ref())?;
81 Ok(Self::new(Method::PATCH, uri))
82 }
83
84 pub fn head(uri: impl AsRef<str>) -> Result<Self, OxiHttpError> {
86 let uri = Uri::try_from(uri.as_ref())?;
87 Ok(Self::new(Method::HEAD, uri))
88 }
89
90 pub fn header(
92 mut self,
93 name: impl AsRef<str>,
94 value: impl AsRef<str>,
95 ) -> Result<Self, OxiHttpError> {
96 let name = HeaderName::try_from(name.as_ref())
97 .map_err(|e| OxiHttpError::InvalidHeader(e.to_string()))?;
98 let value = HeaderValue::try_from(value.as_ref())
99 .map_err(|e| OxiHttpError::InvalidHeader(e.to_string()))?;
100 self.headers.insert(name, value);
101 Ok(self)
102 }
103
104 pub fn headers(mut self, headers: HeaderMap) -> Self {
106 for (k, v) in &headers {
107 self.headers.insert(k.clone(), v.clone());
108 }
109 self
110 }
111
112 pub fn body(mut self, body: impl Into<Body>) -> Self {
114 self.body = body.into();
115 self
116 }
117
118 pub fn json<T: serde::Serialize>(mut self, value: &T) -> Result<Self, OxiHttpError> {
120 let bytes = serde_json::to_vec(value).map_err(|e| OxiHttpError::Json(e.to_string()))?;
121 self.headers.insert(
122 http::header::CONTENT_TYPE,
123 HeaderValue::from_static("application/json"),
124 );
125 self.body = Body::full(Bytes::from(bytes));
126 Ok(self)
127 }
128
129 pub fn form(mut self, body: crate::FormBody) -> Self {
135 self.headers.insert(
136 http::header::CONTENT_TYPE,
137 HeaderValue::from_static("application/x-www-form-urlencoded"),
138 );
139 self.body = Body::full(body.build());
140 self
141 }
142
143 pub fn build(self) -> Result<OxiRequest<Body>, OxiHttpError> {
149 let mut builder = http::Request::builder().method(self.method).uri(self.uri);
150 for (k, v) in &self.headers {
151 builder = builder.header(k, v);
152 }
153 builder
154 .body(self.body)
155 .map_err(|e| OxiHttpError::Http(std::sync::Arc::new(e)))
156 }
157}
158
159#[cfg(test)]
160mod tests {
161 use super::*;
162 use crate::FormBody;
163
164 #[test]
165 fn test_get_builder() {
166 let req = RequestBuilder::get("http://example.com/path")
167 .expect("valid URI")
168 .build()
169 .expect("build succeeds");
170 assert_eq!(req.method(), &Method::GET);
171 assert_eq!(req.uri().to_string(), "http://example.com/path");
172 }
173
174 #[test]
175 fn test_post_with_json() {
176 #[derive(serde::Serialize)]
177 struct Payload {
178 key: &'static str,
179 }
180
181 let req = RequestBuilder::post("http://example.com/api")
182 .expect("valid URI")
183 .json(&Payload { key: "value" })
184 .expect("serialises")
185 .build()
186 .expect("build succeeds");
187
188 assert_eq!(req.method(), &Method::POST);
189 assert_eq!(
190 req.headers()
191 .get(http::header::CONTENT_TYPE)
192 .map(|v| v.as_bytes()),
193 Some(b"application/json".as_ref()),
194 );
195 }
196
197 #[test]
198 fn test_headers_merged() {
199 let mut extra = HeaderMap::new();
200 extra.insert(
201 HeaderName::from_static("x-custom"),
202 HeaderValue::from_static("abc"),
203 );
204
205 let req = RequestBuilder::get("http://example.com")
206 .expect("valid URI")
207 .headers(extra)
208 .build()
209 .expect("build succeeds");
210
211 assert_eq!(
212 req.headers().get("x-custom").map(|v| v.as_bytes()),
213 Some(b"abc".as_ref()),
214 );
215 }
216
217 #[test]
218 fn test_form_sets_content_type() {
219 let form = FormBody::new().field("foo", "bar");
220 let req = RequestBuilder::post("http://example.com/form")
221 .expect("valid URI")
222 .form(form)
223 .build()
224 .expect("build succeeds");
225
226 assert_eq!(
227 req.headers()
228 .get(http::header::CONTENT_TYPE)
229 .map(|v| v.as_bytes()),
230 Some(b"application/x-www-form-urlencoded".as_ref()),
231 );
232 }
233
234 #[test]
235 fn test_invalid_uri_returns_error() {
236 let result = RequestBuilder::get("not a valid uri!!!!");
237 assert!(result.is_err());
238 }
239
240 #[test]
241 fn test_all_methods() {
242 let cases: &[(&str, Method)] = &[
243 ("http://a.com", Method::POST),
244 ("http://a.com", Method::PUT),
245 ("http://a.com", Method::DELETE),
246 ("http://a.com", Method::PATCH),
247 ("http://a.com", Method::HEAD),
248 ];
249 for (uri, method) in cases {
250 let req = match method.as_str() {
251 "POST" => RequestBuilder::post(uri),
252 "PUT" => RequestBuilder::put(uri),
253 "DELETE" => RequestBuilder::delete(uri),
254 "PATCH" => RequestBuilder::patch(uri),
255 "HEAD" => RequestBuilder::head(uri),
256 _ => unreachable!(),
257 }
258 .expect("valid uri")
259 .build()
260 .expect("build succeeds");
261 assert_eq!(req.method(), method);
262 }
263 }
264}