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 { 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 { 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 { let config = RequestConfig::get(url.clone(), headers); let client = ClientBuilder::new().build()?; config.build(&client) } }