use super::{BaseHttpClient, Form, Headers, Query};
use std::{io, time::Duration};
use maybe_async::sync_impl;
use serde_json::Value;
use ureq::{Request, Response};
#[derive(thiserror::Error, Debug)]
pub enum UreqError {
#[error("transport: {0}")]
Transport(#[from] ureq::Transport),
#[error("I/O: {0}")]
Io(#[from] io::Error),
#[error("status code {}", ureq::Response::status(.0))]
StatusCode(ureq::Response),
}
#[derive(Debug, Clone)]
pub struct UreqClient {
agent: ureq::Agent,
}
impl Default for UreqClient {
fn default() -> Self {
let agent = ureq::AgentBuilder::new()
.try_proxy_from_env(true)
.timeout(Duration::from_secs(10));
#[cfg(feature = "ureq-native-tls")]
let agent = agent.tls_connector(std::sync::Arc::new(
native_tls::TlsConnector::new().expect("Failed to initialize TLS connector"),
));
Self {
agent: agent.build(),
}
}
}
impl UreqClient {
fn request<D>(
&self,
mut request: Request,
headers: Option<&Headers>,
send_request: D,
) -> Result<String, UreqError>
where
D: Fn(Request) -> Result<Response, ureq::Error>,
{
if let Some(headers) = headers {
for (key, val) in headers.iter() {
request = request.set(key, val);
}
}
log::info!("Making request {:?}", request);
match send_request(request) {
Ok(response) => response.into_string().map_err(Into::into),
Err(err) => match err {
ureq::Error::Status(_, response) => Err(UreqError::StatusCode(response)),
ureq::Error::Transport(transport) => Err(UreqError::Transport(transport)),
},
}
}
}
#[sync_impl]
impl BaseHttpClient for UreqClient {
type Error = UreqError;
#[inline]
fn get(
&self,
url: &str,
headers: Option<&Headers>,
payload: &Query,
) -> Result<String, Self::Error> {
let request = self.agent.get(url);
let sender = |mut req: Request| {
for (key, val) in payload.iter() {
req = req.query(key, val);
}
req.call()
};
self.request(request, headers, sender)
}
#[inline]
fn post(
&self,
url: &str,
headers: Option<&Headers>,
payload: &Value,
) -> Result<String, Self::Error> {
let request = self.agent.post(url);
let sender = |req: Request| req.send_json(payload.clone());
self.request(request, headers, sender)
}
#[inline]
fn post_form(
&self,
url: &str,
headers: Option<&Headers>,
payload: &Form<'_>,
) -> Result<String, Self::Error> {
let request = self.agent.post(url);
let sender = |req: Request| {
let payload = payload
.iter()
.map(|(key, val)| (*key, *val))
.collect::<Vec<_>>();
req.send_form(&payload)
};
self.request(request, headers, sender)
}
#[inline]
fn put(
&self,
url: &str,
headers: Option<&Headers>,
payload: &Value,
) -> Result<String, Self::Error> {
let request = self.agent.put(url);
let sender = |req: Request| req.send_json(payload.clone());
self.request(request, headers, sender)
}
#[inline]
fn delete(
&self,
url: &str,
headers: Option<&Headers>,
payload: &Value,
) -> Result<String, Self::Error> {
let request = self.agent.delete(url);
let sender = |req: Request| req.send_json(payload.clone());
self.request(request, headers, sender)
}
}