use std::convert::TryInto;
use crate::{Client, POSTMARK_API_URL};
use crate::{Endpoint, Query, QueryError};
use async_trait::async_trait;
use bytes::Bytes;
use http::{Request, Response};
use thiserror::Error;
use typed_builder::TypedBuilder;
#[derive(TypedBuilder, Clone)]
pub struct PostmarkClient {
#[builder(default, setter(into, strip_option))]
pub server_token: Option<String>,
#[builder(default, setter(into, strip_option))]
pub account_token: Option<String>,
#[builder(default=POSTMARK_API_URL.into(), setter(into))]
pub base_url: String,
#[builder(default=::reqwest::Client::new(), setter(skip))]
client: reqwest::Client,
}
impl std::fmt::Debug for PostmarkClient {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Self {
server_token: ref _server_token,
account_token: ref _account_token,
base_url: ref _base_url,
client: ref _client,
} = *self;
let mut builder = f.debug_struct("PostmarkClient");
builder.field("server_token", &_server_token.as_ref().map(|_| "***"));
builder.field("account_token", &_account_token.as_ref().map(|_| "***"));
builder.field("base_url", _base_url);
builder.finish()
}
}
impl Default for PostmarkClient {
fn default() -> Self {
Self {
base_url: POSTMARK_API_URL.into(),
server_token: None,
account_token: None,
client: reqwest::Client::new(),
}
}
}
#[derive(Error, Debug)]
pub enum PostmarkClientError {
#[error("error setting auth header: {}", source)]
AuthError {
#[from]
source: http::header::InvalidHeaderValue,
},
#[error("communication with postmark: {}", source)]
Communication {
#[from]
source: reqwest::Error,
},
#[error("`http` error: {}", source)]
Http {
#[from]
source: http::Error,
},
#[error("`Url` error: {}", source)]
UrlParseError {
#[from]
source: url::ParseError,
},
#[error("invalid uri: {}", source)]
InvalidUri {
#[from]
source: http::uri::InvalidUri,
},
}
#[async_trait]
impl Client for PostmarkClient {
type Error = PostmarkClientError;
async fn execute(&self, req: Request<Bytes>) -> Result<Response<Bytes>, Self::Error> {
let mut req = req;
if let Some(tok) = &self.server_token {
req.headers_mut()
.append("X-Postmark-Server-Token", tok.try_into()?);
}
if let Some(tok) = &self.account_token {
req.headers_mut()
.append("X-Postmark-Account-Token", tok.try_into()?);
}
let base_url: url::Url = self.base_url.parse()?;
let url = match req.uri().path_and_query() {
Some(path) => base_url.join(path.as_str())?,
None => base_url,
};
*req.uri_mut() = url.as_str().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().unwrap();
for (k, v) in reqwest_rsp.headers() {
headers.insert(k, v.clone());
}
Ok(rsp.body(reqwest_rsp.bytes().await?)?)
}
}
impl PostmarkClient {
pub async fn execute_endpoint<T>(
&self,
request: T,
) -> Result<T::Response, QueryError<PostmarkClientError>>
where
T: Endpoint + Send + Sync,
{
request.execute(self).await
}
}