use std::fmt;
use std::time::Duration;
use crate::api::{ApiError, Request, BASE_URL};
const TIMEOUT: Duration = Duration::from_secs(10);
pub struct Client {
agent: ureq::Agent,
base_url: String,
api_key: String,
}
#[derive(Debug)]
#[non_exhaustive]
pub enum Error {
Transport(TransportError),
Api(ApiError),
}
#[derive(Debug)]
pub struct TransportError(ureq::Error);
impl Client {
pub fn new(api_key: impl Into<String>) -> Self {
Self {
agent: agent(TIMEOUT),
base_url: BASE_URL.to_owned(),
api_key: api_key.into(),
}
}
pub fn with_timeout(self, timeout: Duration) -> Self {
Self {
agent: agent(timeout),
..self
}
}
pub fn with_base_url(self, base_url: impl Into<String>) -> Self {
Self {
base_url: base_url.into(),
..self
}
}
pub fn send<T>(&self, request: &Request<T>) -> Result<T, Error> {
let call = self
.agent
.get(format!("{}{}", self.base_url, request.path))
.header("X-API-Key", &self.api_key);
let mut response = request
.query
.iter()
.fold(call, |call, (key, value)| call.query(key, value))
.call()
.map_err(failed)?;
let body = response.body_mut().read_to_string().map_err(failed)?;
Ok(request.decode(response.status().as_u16(), &body)?)
}
}
fn agent(timeout: Duration) -> ureq::Agent {
ureq::Agent::config_builder()
.http_status_as_error(false)
.max_redirects(0)
.timeout_global(Some(timeout))
.build()
.into()
}
fn failed(error: ureq::Error) -> Error {
Error::Transport(TransportError(error))
}
impl fmt::Display for TransportError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}
impl std::error::Error for TransportError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
Some(&self.0)
}
}
impl From<ApiError> for Error {
fn from(error: ApiError) -> Self {
Self::Api(error)
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Transport(error) => error.fmt(f),
Self::Api(error) => error.fmt(f),
}
}
}
impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::Transport(error) => Some(error),
Self::Api(error) => Some(error),
}
}
}