aboutsummaryrefslogtreecommitdiff
path: root/src/http.rs
diff options
context:
space:
mode:
authorGus Power <gus@infinitesidequests.com>2025-05-22 16:21:05 +0100
committerGus Power <gus@infinitesidequests.com>2025-05-22 16:21:05 +0100
commit3e5aa28345bb009c12b5a55f2e7174957bf4ed9a (patch)
treefa9044d9e72e213aed22d1761a2bf01a439a0550 /src/http.rs
parente417f3afd13fa770a3b64d604bb1686ed6a77203 (diff)
started on arbitrary http endpoint configuration
Diffstat (limited to 'src/http.rs')
-rw-r--r--src/http.rs97
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)
+ }
+
+}