use std::convert::TryInto;
use std::time::Duration;
use crate::{Client, Endpoint, LETTERMINT_API_URL, Query, QueryError};
use bytes::Bytes;
use http::{Request, Response};
use thiserror::Error;
const DEFAULT_TIMEOUT: Duration = Duration::from_secs(30);
const USER_AGENT: &str = concat!("Lettermint/", env!("CARGO_PKG_VERSION"), " (Rust)");
#[derive(Clone)]
pub struct LettermintClient {
api_token: String,
base_url: String,
client: ::reqwest::Client,
}
fn default_reqwest_client() -> ::reqwest::Client {
::reqwest::Client::builder()
.timeout(DEFAULT_TIMEOUT)
.user_agent(USER_AGENT)
.build()
.expect("default reqwest client should build")
}
impl LettermintClient {
pub fn new(api_token: impl Into<String>) -> Self {
Self {
api_token: api_token.into(),
base_url: LETTERMINT_API_URL.into(),
client: default_reqwest_client(),
}
}
pub fn with_base_url(api_token: impl Into<String>, base_url: impl Into<String>) -> Self {
Self {
api_token: api_token.into(),
base_url: base_url.into(),
client: default_reqwest_client(),
}
}
pub fn with_reqwest_client(api_token: impl Into<String>, client: ::reqwest::Client) -> Self {
Self {
api_token: api_token.into(),
base_url: LETTERMINT_API_URL.into(),
client,
}
}
pub async fn execute_endpoint<T>(
&self,
request: T,
) -> Result<T::Response, QueryError<LettermintClientError>>
where
T: Endpoint + Send + Sync,
{
request.execute(self).await
}
}
impl std::fmt::Debug for LettermintClient {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("LettermintClient")
.field("api_token", &"***")
.field("base_url", &self.base_url)
.finish()
}
}
#[derive(Error, Debug)]
pub enum LettermintClientError {
#[error("error setting auth header: {}", source)]
AuthError {
#[from]
source: http::header::InvalidHeaderValue,
},
#[error("communication with lettermint: {}", source)]
Communication {
#[from]
source: ::reqwest::Error,
},
#[error("http error: {}", source)]
Http {
#[from]
source: http::Error,
},
#[error("invalid uri: {}", source)]
InvalidUri {
#[from]
source: http::uri::InvalidUri,
},
}
impl Client for LettermintClient {
type Error = LettermintClientError;
async fn execute(&self, mut req: Request<Bytes>) -> Result<Response<Bytes>, Self::Error> {
req.headers_mut()
.append("x-lettermint-token", self.api_token.as_str().try_into()?);
let path = req
.uri()
.path_and_query()
.map(|pq| pq.as_str())
.unwrap_or("");
let url = format!(
"{}/{}",
self.base_url.trim_end_matches('/'),
path.trim_start_matches('/')
);
*req.uri_mut() = url.parse()?;
let reqwest_req: ::reqwest::Request = req.try_into()?;
let reqwest_rsp = self.client.execute(reqwest_req).await?;
let mut rsp = Response::builder()
.status(reqwest_rsp.status())
.version(reqwest_rsp.version());
let headers = rsp
.headers_mut()
.expect("response builder should have headers");
for (k, v) in reqwest_rsp.headers() {
headers.insert(k, v.clone());
}
Ok(rsp.body(reqwest_rsp.bytes().await?)?)
}
}