diff options
| author | Gus Power <gus@infinitesidequests.com> | 2025-05-22 16:21:05 +0100 |
|---|---|---|
| committer | Gus Power <gus@infinitesidequests.com> | 2025-05-22 16:21:05 +0100 |
| commit | 3e5aa28345bb009c12b5a55f2e7174957bf4ed9a (patch) | |
| tree | fa9044d9e72e213aed22d1761a2bf01a439a0550 /src/http.rs | |
| parent | e417f3afd13fa770a3b64d604bb1686ed6a77203 (diff) | |
started on arbitrary http endpoint configuration
Diffstat (limited to 'src/http.rs')
| -rw-r--r-- | src/http.rs | 97 |
1 files changed, 97 insertions, 0 deletions
diff --git a/src/http.rs b/src/http.rs new file mode 100644 index 0000000..db5e3a6 --- /dev/null +++ b/src/http.rs @@ -0,0 +1,97 @@ +use serde_with::DisplayFromStr; +use http::{HeaderMap, HeaderName, HeaderValue, Method}; +use reqwest::{Client, Request, Url}; +use serde::{Deserialize, Serialize}; +use serde_with::serde_as; +use crate::error::{AppError, AppResult}; + +#[serde_as] +#[derive(Debug, Serialize, Deserialize, PartialEq)] +pub struct RequestConfig { + #[serde_as(as = "DisplayFromStr")] + #[serde(default = "RequestConfig::default_method")] + method: Method, + #[serde_as(as = "DisplayFromStr")] + url: Url, + #[serde(default)] + headers: Vec<(String, String)>, +} + +impl RequestConfig { + + pub fn new(method: Method, url: Url, headers: Vec<(String, String)>) -> Self { + Self { method, url, headers } + } + + pub fn get(url: Url, headers: Vec<(String, String)>) -> Self { + Self::new(Method::GET, url, headers) + } + + pub fn build(&self, client: &Client) -> AppResult<Request> { + let mut builder = client.request(self.method.clone(), self.url.clone()); + if !self.headers.is_empty() { + builder = builder.headers(self.header_map()?); + } + Ok(builder.build()?) + } + + fn default_method() -> Method { + Method::GET + } + + fn header_map(&self) -> AppResult<HeaderMap> { + let mut header_map = HeaderMap::new(); + for (name, value) in &self.headers { + let name: HeaderName = name.try_into().map_err(|e| { + AppError::InvalidHttpHeader(format!("[{}] is not a valid HTTP header name ({})", name, e)) + })?; + let value: HeaderValue = value.parse().map_err(|e| { + AppError::InvalidHttpHeader(format!("[{}] is not a valid HTTP header value ({})", value, e)) + })?; + header_map.insert(name, value); + } + + Ok(header_map) + } + +} + +#[cfg(test)] +mod tests { + use crate::AppError; + use crate::{assert_error, assert_invalid_http_header, test}; + use reqwest::ClientBuilder; + use super::*; + + test! { + fn simple_get_request() { + let url = Url::parse("https://example.com")?; + let actual = build_get_request(&url, vec![])?; + assert_eq!(actual.method(), Method::GET); + assert_eq!(actual.url(), &url); + } + } + + test! { + fn invalid_header_name() { + let url = Url::parse("https://example.com")?; + let invalid_headers = vec![("Content\x00Type".to_string(), "application/json".to_string())]; + assert_invalid_http_header!(build_get_request(&url, invalid_headers), "[Content\x00Type] is not a valid HTTP header name"); + } + } + + test! { + fn invalid_header_value() { + let url = Url::parse("https://example.com")?; + let invalid_headers = vec![("Content-Type".to_string(), "application/json\x00".to_string())]; + assert_invalid_http_header!(build_get_request(&url, invalid_headers), "[application/json\x00] is not a valid HTTP header value"); + } + } + + fn build_get_request(url: &Url, headers: Vec<(String, String)>) -> AppResult<Request> { + let config = RequestConfig::get(url.clone(), headers); + let client = ClientBuilder::new().build()?; + config.build(&client) + } + +} |
