|
|
@@ -0,0 +1,670 @@
|
|
|
+//! HTTP utilities for common HTTP operations
|
|
|
+
|
|
|
+use std::fmt::Debug;
|
|
|
+
|
|
|
+use serde::de::DeserializeOwned;
|
|
|
+use serde::Serialize;
|
|
|
+
|
|
|
+use crate::error::Error;
|
|
|
+
|
|
|
+// Re-export Response type for crates that need to do custom response handling
|
|
|
+pub use bitreq::Response;
|
|
|
+
|
|
|
+/// Default connection pool size
|
|
|
+const DEFAULT_POOL_SIZE: usize = 10;
|
|
|
+
|
|
|
+/// Proxy configuration wrapper
|
|
|
+#[cfg(not(target_arch = "wasm32"))]
|
|
|
+#[derive(Debug, Clone)]
|
|
|
+struct ProxyConfig {
|
|
|
+ proxy: bitreq::Proxy,
|
|
|
+ #[allow(dead_code)]
|
|
|
+ accept_invalid_certs: bool,
|
|
|
+}
|
|
|
+
|
|
|
+/// Clonable HTTP client with connection pooling and proxy support
|
|
|
+#[derive(Clone)]
|
|
|
+pub struct HttpClient {
|
|
|
+ client: bitreq::Client,
|
|
|
+ #[cfg(not(target_arch = "wasm32"))]
|
|
|
+ proxy_per_url: std::collections::HashMap<String, (regex::Regex, ProxyConfig)>,
|
|
|
+ #[cfg(not(target_arch = "wasm32"))]
|
|
|
+ all_proxy: Option<ProxyConfig>,
|
|
|
+}
|
|
|
+
|
|
|
+impl HttpClient {
|
|
|
+ /// Create new HttpClient with default pool size (10)
|
|
|
+ pub fn new() -> Self {
|
|
|
+ Self::with_pool_size(DEFAULT_POOL_SIZE)
|
|
|
+ }
|
|
|
+
|
|
|
+ /// Create with custom pool size
|
|
|
+ pub fn with_pool_size(size: usize) -> Self {
|
|
|
+ Self {
|
|
|
+ client: bitreq::Client::new(size),
|
|
|
+ #[cfg(not(target_arch = "wasm32"))]
|
|
|
+ proxy_per_url: std::collections::HashMap::new(),
|
|
|
+ #[cfg(not(target_arch = "wasm32"))]
|
|
|
+ all_proxy: None,
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// Set global proxy for all requests
|
|
|
+ ///
|
|
|
+ /// # Arguments
|
|
|
+ /// * `proxy` - Proxy URL (e.g., "http://user:pass@localhost:8080")
|
|
|
+ /// * `accept_invalid_certs` - Whether to accept invalid certificates (currently unused)
|
|
|
+ ///
|
|
|
+ /// # Errors
|
|
|
+ /// Returns an error if the proxy URL is invalid
|
|
|
+ #[cfg(not(target_arch = "wasm32"))]
|
|
|
+ pub fn with_proxy(&mut self, proxy: url::Url, accept_invalid_certs: bool) -> Result<(), Error> {
|
|
|
+ // Strip trailing slash as bitreq's proxy parser doesn't handle it
|
|
|
+ let proxy_str = proxy.as_str().trim_end_matches('/');
|
|
|
+ let proxy = bitreq::Proxy::new_http(proxy_str)
|
|
|
+ .map_err(|e| Error::HttpError(None, format!("Invalid proxy: {}", e)))?;
|
|
|
+ self.all_proxy = Some(ProxyConfig {
|
|
|
+ proxy,
|
|
|
+ accept_invalid_certs,
|
|
|
+ });
|
|
|
+ Ok(())
|
|
|
+ }
|
|
|
+
|
|
|
+ /// Set proxy for URLs matching a regex pattern
|
|
|
+ ///
|
|
|
+ /// # Arguments
|
|
|
+ /// * `pattern` - Regex pattern to match URLs against
|
|
|
+ /// * `proxy` - Proxy URL (e.g., "http://user:pass@localhost:8080")
|
|
|
+ /// * `accept_invalid_certs` - Whether to accept invalid certificates (currently unused)
|
|
|
+ ///
|
|
|
+ /// # Errors
|
|
|
+ /// Returns an error if the pattern is invalid or the proxy URL is invalid
|
|
|
+ #[cfg(not(target_arch = "wasm32"))]
|
|
|
+ pub fn with_proxy_for_pattern(
|
|
|
+ &mut self,
|
|
|
+ pattern: &str,
|
|
|
+ proxy: url::Url,
|
|
|
+ accept_invalid_certs: bool,
|
|
|
+ ) -> Result<(), Error> {
|
|
|
+ let regex = regex::Regex::new(pattern)
|
|
|
+ .map_err(|e| Error::HttpError(None, format!("Invalid regex pattern: {}", e)))?;
|
|
|
+ // Strip trailing slash as bitreq's proxy parser doesn't handle it
|
|
|
+ let proxy_str = proxy.as_str().trim_end_matches('/');
|
|
|
+ let proxy = bitreq::Proxy::new_http(proxy_str)
|
|
|
+ .map_err(|e| Error::HttpError(None, format!("Invalid proxy: {}", e)))?;
|
|
|
+ self.proxy_per_url.insert(
|
|
|
+ pattern.to_string(),
|
|
|
+ (
|
|
|
+ regex,
|
|
|
+ ProxyConfig {
|
|
|
+ proxy,
|
|
|
+ accept_invalid_certs,
|
|
|
+ },
|
|
|
+ ),
|
|
|
+ );
|
|
|
+ Ok(())
|
|
|
+ }
|
|
|
+
|
|
|
+ /// Prepare a request with appropriate proxy settings
|
|
|
+ #[cfg(not(target_arch = "wasm32"))]
|
|
|
+ fn prepare_request(&self, req: bitreq::Request, url: &str) -> bitreq::Request {
|
|
|
+ // Check per-URL proxies first
|
|
|
+ for (_, (pattern, proxy_config)) in &self.proxy_per_url {
|
|
|
+ if pattern.is_match(url) {
|
|
|
+ return req.with_proxy(proxy_config.proxy.clone());
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // Fall back to global proxy
|
|
|
+ if let Some(proxy_config) = &self.all_proxy {
|
|
|
+ return req.with_proxy(proxy_config.proxy.clone());
|
|
|
+ }
|
|
|
+ req
|
|
|
+ }
|
|
|
+
|
|
|
+ /// Prepare a request (no-op for wasm32)
|
|
|
+ #[cfg(target_arch = "wasm32")]
|
|
|
+ fn prepare_request(&self, req: bitreq::Request, _url: &str) -> bitreq::Request {
|
|
|
+ req
|
|
|
+ }
|
|
|
+
|
|
|
+ /// Perform a GET request and deserialize the JSON response
|
|
|
+ ///
|
|
|
+ /// # Arguments
|
|
|
+ /// * `url` - The URL to fetch
|
|
|
+ ///
|
|
|
+ /// # Returns
|
|
|
+ /// The deserialized response of type `R`
|
|
|
+ ///
|
|
|
+ /// # Errors
|
|
|
+ /// Returns an error if the request fails, returns non-200 status, or JSON parsing fails
|
|
|
+ pub async fn get<R: DeserializeOwned>(&self, url: &str) -> Result<R, Error> {
|
|
|
+ let request = self.prepare_request(bitreq::get(url), url);
|
|
|
+ let response = self
|
|
|
+ .client
|
|
|
+ .send_async(request)
|
|
|
+ .await
|
|
|
+ .map_err(|e: bitreq::Error| Error::HttpError(None, e.to_string()))?;
|
|
|
+
|
|
|
+ handle_response(response)
|
|
|
+ }
|
|
|
+
|
|
|
+ /// Perform a POST request with a JSON body and deserialize the JSON response
|
|
|
+ ///
|
|
|
+ /// # Arguments
|
|
|
+ /// * `url` - The URL to post to
|
|
|
+ /// * `body` - The body to serialize as JSON
|
|
|
+ ///
|
|
|
+ /// # Returns
|
|
|
+ /// The deserialized response of type `R`
|
|
|
+ ///
|
|
|
+ /// # Errors
|
|
|
+ /// Returns an error if the request fails, returns non-200 status, or JSON parsing fails
|
|
|
+ pub async fn post_json<T: Serialize, R: DeserializeOwned>(
|
|
|
+ &self,
|
|
|
+ url: &str,
|
|
|
+ body: &T,
|
|
|
+ ) -> Result<R, Error> {
|
|
|
+ let json_body = serde_json::to_string(body)?;
|
|
|
+
|
|
|
+ let request = bitreq::post(url)
|
|
|
+ .with_body(json_body)
|
|
|
+ .with_header("Content-Type", "application/json; charset=UTF-8");
|
|
|
+ let request = self.prepare_request(request, url);
|
|
|
+
|
|
|
+ let response = self
|
|
|
+ .client
|
|
|
+ .send_async(request)
|
|
|
+ .await
|
|
|
+ .map_err(|e: bitreq::Error| Error::HttpError(None, e.to_string()))?;
|
|
|
+
|
|
|
+ handle_response(response)
|
|
|
+ }
|
|
|
+
|
|
|
+ /// Perform a POST request with form-encoded body and deserialize the JSON response
|
|
|
+ ///
|
|
|
+ /// # Arguments
|
|
|
+ /// * `url` - The URL to post to
|
|
|
+ /// * `body` - Key-value pairs to encode as form data
|
|
|
+ ///
|
|
|
+ /// # Returns
|
|
|
+ /// The deserialized response of type `R`
|
|
|
+ ///
|
|
|
+ /// # Errors
|
|
|
+ /// Returns an error if the request fails, returns non-200 status, or JSON parsing fails
|
|
|
+ pub async fn post_form<K, V, R>(&self, url: &str, body: &[(K, V)]) -> Result<R, Error>
|
|
|
+ where
|
|
|
+ K: ToString,
|
|
|
+ V: ToString,
|
|
|
+ R: DeserializeOwned,
|
|
|
+ {
|
|
|
+ let form_body = url::form_urlencoded::Serializer::new(String::new())
|
|
|
+ .extend_pairs(body.iter().map(|(k, v)| (k.to_string(), v.to_string())))
|
|
|
+ .finish();
|
|
|
+
|
|
|
+ let request = bitreq::post(url)
|
|
|
+ .with_header("Content-Type", "application/x-www-form-urlencoded")
|
|
|
+ .with_body(form_body);
|
|
|
+ let request = self.prepare_request(request, url);
|
|
|
+
|
|
|
+ let response = self
|
|
|
+ .client
|
|
|
+ .send_async(request)
|
|
|
+ .await
|
|
|
+ .map_err(|e: bitreq::Error| Error::HttpError(None, e.to_string()))?;
|
|
|
+
|
|
|
+ handle_response(response)
|
|
|
+ }
|
|
|
+
|
|
|
+ /// Perform a GET request with custom headers and deserialize the JSON response
|
|
|
+ ///
|
|
|
+ /// # Arguments
|
|
|
+ /// * `url` - The URL to fetch
|
|
|
+ /// * `headers` - Custom headers as key-value pairs
|
|
|
+ ///
|
|
|
+ /// # Returns
|
|
|
+ /// The deserialized response of type `R`
|
|
|
+ ///
|
|
|
+ /// # Errors
|
|
|
+ /// Returns an error if the request fails, returns non-200 status, or JSON parsing fails
|
|
|
+ pub async fn get_with_headers<R: DeserializeOwned>(
|
|
|
+ &self,
|
|
|
+ url: &str,
|
|
|
+ headers: &[(&str, &str)],
|
|
|
+ ) -> Result<R, Error> {
|
|
|
+ let mut request = bitreq::get(url);
|
|
|
+ for (key, value) in headers {
|
|
|
+ request = request.with_header(*key, *value);
|
|
|
+ }
|
|
|
+ let request = self.prepare_request(request, url);
|
|
|
+
|
|
|
+ let response = self
|
|
|
+ .client
|
|
|
+ .send_async(request)
|
|
|
+ .await
|
|
|
+ .map_err(|e: bitreq::Error| Error::HttpError(None, e.to_string()))?;
|
|
|
+
|
|
|
+ handle_response(response)
|
|
|
+ }
|
|
|
+
|
|
|
+ /// Perform a POST request with JSON body and custom headers
|
|
|
+ ///
|
|
|
+ /// # Arguments
|
|
|
+ /// * `url` - The URL to post to
|
|
|
+ /// * `body` - The body to serialize as JSON
|
|
|
+ /// * `headers` - Custom headers as key-value pairs
|
|
|
+ ///
|
|
|
+ /// # Returns
|
|
|
+ /// The deserialized response of type `R`
|
|
|
+ ///
|
|
|
+ /// # Errors
|
|
|
+ /// Returns an error if the request fails, returns non-200 status, or JSON parsing fails
|
|
|
+ pub async fn post_json_with_headers<T: Serialize, R: DeserializeOwned>(
|
|
|
+ &self,
|
|
|
+ url: &str,
|
|
|
+ body: &T,
|
|
|
+ headers: &[(&str, &str)],
|
|
|
+ ) -> Result<R, Error> {
|
|
|
+ let json_body = serde_json::to_string(body)?;
|
|
|
+
|
|
|
+ let mut request = bitreq::post(url)
|
|
|
+ .with_body(json_body)
|
|
|
+ .with_header("Content-Type", "application/json; charset=UTF-8");
|
|
|
+
|
|
|
+ for (key, value) in headers {
|
|
|
+ request = request.with_header(*key, *value);
|
|
|
+ }
|
|
|
+ let request = self.prepare_request(request, url);
|
|
|
+
|
|
|
+ let response = self
|
|
|
+ .client
|
|
|
+ .send_async(request)
|
|
|
+ .await
|
|
|
+ .map_err(|e: bitreq::Error| Error::HttpError(None, e.to_string()))?;
|
|
|
+
|
|
|
+ handle_response(response)
|
|
|
+ }
|
|
|
+
|
|
|
+ /// Perform a POST request with form-encoded body and custom headers
|
|
|
+ ///
|
|
|
+ /// # Arguments
|
|
|
+ /// * `url` - The URL to post to
|
|
|
+ /// * `body` - Key-value pairs to encode as form data
|
|
|
+ /// * `headers` - Custom headers as key-value pairs
|
|
|
+ ///
|
|
|
+ /// # Returns
|
|
|
+ /// The deserialized response of type `R`
|
|
|
+ ///
|
|
|
+ /// # Errors
|
|
|
+ /// Returns an error if the request fails, returns non-200 status, or JSON parsing fails
|
|
|
+ pub async fn post_form_with_headers<K, V, R>(
|
|
|
+ &self,
|
|
|
+ url: &str,
|
|
|
+ body: &[(K, V)],
|
|
|
+ headers: &[(&str, &str)],
|
|
|
+ ) -> Result<R, Error>
|
|
|
+ where
|
|
|
+ K: ToString,
|
|
|
+ V: ToString,
|
|
|
+ R: DeserializeOwned,
|
|
|
+ {
|
|
|
+ let form_body = url::form_urlencoded::Serializer::new(String::new())
|
|
|
+ .extend_pairs(body.iter().map(|(k, v)| (k.to_string(), v.to_string())))
|
|
|
+ .finish();
|
|
|
+
|
|
|
+ let mut request = bitreq::post(url)
|
|
|
+ .with_header("Content-Type", "application/x-www-form-urlencoded")
|
|
|
+ .with_body(form_body);
|
|
|
+
|
|
|
+ for (key, value) in headers {
|
|
|
+ request = request.with_header(*key, *value);
|
|
|
+ }
|
|
|
+ let request = self.prepare_request(request, url);
|
|
|
+
|
|
|
+ let response = self
|
|
|
+ .client
|
|
|
+ .send_async(request)
|
|
|
+ .await
|
|
|
+ .map_err(|e: bitreq::Error| Error::HttpError(None, e.to_string()))?;
|
|
|
+
|
|
|
+ handle_response(response)
|
|
|
+ }
|
|
|
+
|
|
|
+ /// Perform a PATCH request with JSON body
|
|
|
+ ///
|
|
|
+ /// # Arguments
|
|
|
+ /// * `url` - The URL to patch
|
|
|
+ /// * `body` - The body to serialize as JSON
|
|
|
+ ///
|
|
|
+ /// # Returns
|
|
|
+ /// The deserialized response of type `R`
|
|
|
+ ///
|
|
|
+ /// # Errors
|
|
|
+ /// Returns an error if the request fails, returns non-200 status, or JSON parsing fails
|
|
|
+ pub async fn patch_json<T: Serialize, R: DeserializeOwned>(
|
|
|
+ &self,
|
|
|
+ url: &str,
|
|
|
+ body: &T,
|
|
|
+ ) -> Result<R, Error> {
|
|
|
+ let json_body = serde_json::to_string(body)?;
|
|
|
+
|
|
|
+ let request = bitreq::patch(url)
|
|
|
+ .with_body(json_body)
|
|
|
+ .with_header("Content-Type", "application/json; charset=UTF-8");
|
|
|
+ let request = self.prepare_request(request, url);
|
|
|
+
|
|
|
+ let response = self
|
|
|
+ .client
|
|
|
+ .send_async(request)
|
|
|
+ .await
|
|
|
+ .map_err(|e: bitreq::Error| Error::HttpError(None, e.to_string()))?;
|
|
|
+
|
|
|
+ handle_response(response)
|
|
|
+ }
|
|
|
+
|
|
|
+ /// Perform a PATCH request with JSON body and custom headers
|
|
|
+ ///
|
|
|
+ /// # Arguments
|
|
|
+ /// * `url` - The URL to patch
|
|
|
+ /// * `body` - The body to serialize as JSON
|
|
|
+ /// * `headers` - Custom headers as key-value pairs
|
|
|
+ ///
|
|
|
+ /// # Returns
|
|
|
+ /// The deserialized response of type `R`
|
|
|
+ ///
|
|
|
+ /// # Errors
|
|
|
+ /// Returns an error if the request fails, returns non-200 status, or JSON parsing fails
|
|
|
+ pub async fn patch_json_with_headers<T: Serialize, R: DeserializeOwned>(
|
|
|
+ &self,
|
|
|
+ url: &str,
|
|
|
+ body: &T,
|
|
|
+ headers: &[(&str, &str)],
|
|
|
+ ) -> Result<R, Error> {
|
|
|
+ let json_body = serde_json::to_string(body)?;
|
|
|
+
|
|
|
+ let mut request = bitreq::patch(url)
|
|
|
+ .with_body(json_body)
|
|
|
+ .with_header("Content-Type", "application/json; charset=UTF-8");
|
|
|
+
|
|
|
+ for (key, value) in headers {
|
|
|
+ request = request.with_header(*key, *value);
|
|
|
+ }
|
|
|
+ let request = self.prepare_request(request, url);
|
|
|
+
|
|
|
+ let response = self
|
|
|
+ .client
|
|
|
+ .send_async(request)
|
|
|
+ .await
|
|
|
+ .map_err(|e: bitreq::Error| Error::HttpError(None, e.to_string()))?;
|
|
|
+
|
|
|
+ handle_response(response)
|
|
|
+ }
|
|
|
+
|
|
|
+ /// Perform a GET request and return raw response
|
|
|
+ ///
|
|
|
+ /// # Arguments
|
|
|
+ /// * `url` - The URL to fetch
|
|
|
+ ///
|
|
|
+ /// # Returns
|
|
|
+ /// The raw response
|
|
|
+ ///
|
|
|
+ /// # Errors
|
|
|
+ /// Returns an error if the request fails
|
|
|
+ pub async fn get_raw(&self, url: &str) -> Result<bitreq::Response, Error> {
|
|
|
+ let request = self.prepare_request(bitreq::get(url), url);
|
|
|
+ self.client
|
|
|
+ .send_async(request)
|
|
|
+ .await
|
|
|
+ .map_err(|e: bitreq::Error| Error::HttpError(None, e.to_string()))
|
|
|
+ }
|
|
|
+
|
|
|
+ /// Perform a GET request with custom headers and return raw response
|
|
|
+ ///
|
|
|
+ /// # Arguments
|
|
|
+ /// * `url` - The URL to fetch
|
|
|
+ /// * `headers` - Custom headers as key-value pairs
|
|
|
+ ///
|
|
|
+ /// # Returns
|
|
|
+ /// The raw response
|
|
|
+ ///
|
|
|
+ /// # Errors
|
|
|
+ /// Returns an error if the request fails
|
|
|
+ pub async fn get_raw_with_headers(
|
|
|
+ &self,
|
|
|
+ url: &str,
|
|
|
+ headers: &[(&str, &str)],
|
|
|
+ ) -> Result<bitreq::Response, Error> {
|
|
|
+ let mut request = bitreq::get(url);
|
|
|
+ for (key, value) in headers {
|
|
|
+ request = request.with_header(*key, *value);
|
|
|
+ }
|
|
|
+ let request = self.prepare_request(request, url);
|
|
|
+ self.client
|
|
|
+ .send_async(request)
|
|
|
+ .await
|
|
|
+ .map_err(|e: bitreq::Error| Error::HttpError(None, e.to_string()))
|
|
|
+ }
|
|
|
+
|
|
|
+ /// Perform a POST request with form-encoded body and return raw response
|
|
|
+ ///
|
|
|
+ /// # Arguments
|
|
|
+ /// * `url` - The URL to post to
|
|
|
+ /// * `body` - Key-value pairs to encode as form data
|
|
|
+ /// * `headers` - Custom headers as key-value pairs
|
|
|
+ ///
|
|
|
+ /// # Returns
|
|
|
+ /// The raw response
|
|
|
+ ///
|
|
|
+ /// # Errors
|
|
|
+ /// Returns an error if the request fails
|
|
|
+ pub async fn post_form_raw_with_headers<K, V>(
|
|
|
+ &self,
|
|
|
+ url: &str,
|
|
|
+ body: &[(K, V)],
|
|
|
+ headers: &[(&str, &str)],
|
|
|
+ ) -> Result<bitreq::Response, Error>
|
|
|
+ where
|
|
|
+ K: ToString,
|
|
|
+ V: ToString,
|
|
|
+ {
|
|
|
+ let form_body = url::form_urlencoded::Serializer::new(String::new())
|
|
|
+ .extend_pairs(body.iter().map(|(k, v)| (k.to_string(), v.to_string())))
|
|
|
+ .finish();
|
|
|
+
|
|
|
+ let mut request = bitreq::post(url)
|
|
|
+ .with_header("Content-Type", "application/x-www-form-urlencoded")
|
|
|
+ .with_body(form_body);
|
|
|
+
|
|
|
+ for (key, value) in headers {
|
|
|
+ request = request.with_header(*key, *value);
|
|
|
+ }
|
|
|
+ let request = self.prepare_request(request, url);
|
|
|
+
|
|
|
+ self.client
|
|
|
+ .send_async(request)
|
|
|
+ .await
|
|
|
+ .map_err(|e: bitreq::Error| Error::HttpError(None, e.to_string()))
|
|
|
+ }
|
|
|
+
|
|
|
+ /// Perform a POST request with JSON body and return raw response
|
|
|
+ ///
|
|
|
+ /// # Arguments
|
|
|
+ /// * `url` - The URL to post to
|
|
|
+ /// * `body` - The body to serialize as JSON
|
|
|
+ ///
|
|
|
+ /// # Returns
|
|
|
+ /// The raw response
|
|
|
+ ///
|
|
|
+ /// # Errors
|
|
|
+ /// Returns an error if the request fails
|
|
|
+ pub async fn post_json_raw<T: Serialize>(
|
|
|
+ &self,
|
|
|
+ url: &str,
|
|
|
+ body: &T,
|
|
|
+ ) -> Result<bitreq::Response, Error> {
|
|
|
+ let json_body = serde_json::to_string(body)?;
|
|
|
+
|
|
|
+ let request = bitreq::post(url)
|
|
|
+ .with_body(json_body)
|
|
|
+ .with_header("Content-Type", "application/json; charset=UTF-8");
|
|
|
+ let request = self.prepare_request(request, url);
|
|
|
+
|
|
|
+ self.client
|
|
|
+ .send_async(request)
|
|
|
+ .await
|
|
|
+ .map_err(|e: bitreq::Error| Error::HttpError(None, e.to_string()))
|
|
|
+ }
|
|
|
+
|
|
|
+ /// Perform a POST request with JSON body and custom headers and return raw response
|
|
|
+ ///
|
|
|
+ /// # Arguments
|
|
|
+ /// * `url` - The URL to post to
|
|
|
+ /// * `body` - The body to serialize as JSON
|
|
|
+ /// * `headers` - Custom headers as key-value pairs
|
|
|
+ ///
|
|
|
+ /// # Returns
|
|
|
+ /// The raw response
|
|
|
+ ///
|
|
|
+ /// # Errors
|
|
|
+ /// Returns an error if the request fails
|
|
|
+ pub async fn post_json_raw_with_headers<T: Serialize>(
|
|
|
+ &self,
|
|
|
+ url: &str,
|
|
|
+ body: &T,
|
|
|
+ headers: &[(&str, &str)],
|
|
|
+ ) -> Result<bitreq::Response, Error> {
|
|
|
+ let json_body = serde_json::to_string(body)?;
|
|
|
+
|
|
|
+ let mut request = bitreq::post(url)
|
|
|
+ .with_body(json_body)
|
|
|
+ .with_header("Content-Type", "application/json; charset=UTF-8");
|
|
|
+
|
|
|
+ for (key, value) in headers {
|
|
|
+ request = request.with_header(*key, *value);
|
|
|
+ }
|
|
|
+ let request = self.prepare_request(request, url);
|
|
|
+
|
|
|
+ self.client
|
|
|
+ .send_async(request)
|
|
|
+ .await
|
|
|
+ .map_err(|e: bitreq::Error| Error::HttpError(None, e.to_string()))
|
|
|
+ }
|
|
|
+
|
|
|
+ /// Perform a POST request with a pre-serialized JSON body string and custom headers
|
|
|
+ ///
|
|
|
+ /// # Arguments
|
|
|
+ /// * `url` - The URL to post to
|
|
|
+ /// * `json_body` - The pre-serialized JSON body string
|
|
|
+ /// * `headers` - Custom headers as key-value pairs
|
|
|
+ ///
|
|
|
+ /// # Returns
|
|
|
+ /// The raw response
|
|
|
+ ///
|
|
|
+ /// # Errors
|
|
|
+ /// Returns an error if the request fails
|
|
|
+ pub async fn post_body_with_headers(
|
|
|
+ &self,
|
|
|
+ url: &str,
|
|
|
+ json_body: &str,
|
|
|
+ headers: &[(&str, &str)],
|
|
|
+ ) -> Result<bitreq::Response, Error> {
|
|
|
+ let mut request = bitreq::post(url)
|
|
|
+ .with_body(json_body.to_string())
|
|
|
+ .with_header("Content-Type", "application/json; charset=UTF-8");
|
|
|
+
|
|
|
+ for (key, value) in headers {
|
|
|
+ request = request.with_header(*key, *value);
|
|
|
+ }
|
|
|
+ let request = self.prepare_request(request, url);
|
|
|
+
|
|
|
+ self.client
|
|
|
+ .send_async(request)
|
|
|
+ .await
|
|
|
+ .map_err(|e: bitreq::Error| Error::HttpError(None, e.to_string()))
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+impl Default for HttpClient {
|
|
|
+ fn default() -> Self {
|
|
|
+ Self::new()
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+impl Debug for HttpClient {
|
|
|
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
|
+ write!(f, "HttpClient")
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/// Handle HTTP response, checking status and deserializing JSON
|
|
|
+fn handle_response<R: DeserializeOwned>(response: bitreq::Response) -> Result<R, Error> {
|
|
|
+ if response.status_code != 200 {
|
|
|
+ return Err(Error::HttpError(
|
|
|
+ Some(response.status_code as u16),
|
|
|
+ "".to_string(),
|
|
|
+ ));
|
|
|
+ }
|
|
|
+
|
|
|
+ serde_json::from_slice::<R>(response.as_bytes()).map_err(Error::from)
|
|
|
+}
|
|
|
+
|
|
|
+#[cfg(test)]
|
|
|
+mod tests {
|
|
|
+ use super::*;
|
|
|
+
|
|
|
+ #[test]
|
|
|
+ fn test_module_loads() {
|
|
|
+ // Simple test to verify the module compiles and loads correctly
|
|
|
+ assert!(true);
|
|
|
+ }
|
|
|
+
|
|
|
+ #[test]
|
|
|
+ fn test_http_client_creation() {
|
|
|
+ let client = HttpClient::new();
|
|
|
+ assert_eq!(format!("{:?}", client), "HttpClient");
|
|
|
+ }
|
|
|
+
|
|
|
+ #[test]
|
|
|
+ fn test_http_client_with_pool_size() {
|
|
|
+ let client = HttpClient::with_pool_size(20);
|
|
|
+ assert_eq!(format!("{:?}", client), "HttpClient");
|
|
|
+ }
|
|
|
+
|
|
|
+ #[test]
|
|
|
+ fn test_http_client_clone() {
|
|
|
+ let client = HttpClient::new();
|
|
|
+ let _cloned = client.clone();
|
|
|
+ }
|
|
|
+
|
|
|
+ #[test]
|
|
|
+ fn test_http_client_default() {
|
|
|
+ let _client: HttpClient = Default::default();
|
|
|
+ }
|
|
|
+
|
|
|
+ #[cfg(not(target_arch = "wasm32"))]
|
|
|
+ #[test]
|
|
|
+ fn test_http_client_with_proxy() {
|
|
|
+ let mut client = HttpClient::new();
|
|
|
+ let proxy_url = url::Url::parse("http://localhost:8080").unwrap();
|
|
|
+ let result = client.with_proxy(proxy_url, false);
|
|
|
+ assert!(result.is_ok());
|
|
|
+ }
|
|
|
+
|
|
|
+ #[cfg(not(target_arch = "wasm32"))]
|
|
|
+ #[test]
|
|
|
+ fn test_http_client_with_proxy_pattern() {
|
|
|
+ let mut client = HttpClient::new();
|
|
|
+ let proxy_url = url::Url::parse("http://localhost:8080").unwrap();
|
|
|
+ let result =
|
|
|
+ client.with_proxy_for_pattern(r"https://mint\.example\.com/.*", proxy_url, false);
|
|
|
+ assert!(result.is_ok());
|
|
|
+ }
|
|
|
+
|
|
|
+ #[cfg(not(target_arch = "wasm32"))]
|
|
|
+ #[test]
|
|
|
+ fn test_http_client_invalid_regex_pattern() {
|
|
|
+ let mut client = HttpClient::new();
|
|
|
+ let proxy_url = url::Url::parse("http://localhost:8080").unwrap();
|
|
|
+ let result = client.with_proxy_for_pattern(r"[invalid", proxy_url, false);
|
|
|
+ assert!(result.is_err());
|
|
|
+ }
|
|
|
+}
|