#![doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/README.md"))]
use futures_util::{TryFutureExt, future::ready};
pub use reqwest::Error;
use reqwest::{Client, RequestBuilder, Response, Result};
use serde::{Serialize, de::DeserializeOwned};
const DEFAULT_USER_AGENT: &str = concat!(env!("CARGO_PKG_NAME"), " ", env!("CARGO_PKG_VERSION"));
pub struct ApiClient {
client: Client,
prefix: String,
authentication: Authentication,
}
pub struct ApiClientBuilder {
prefix: String,
authentication: Authentication,
user_agent: Option<String>,
}
impl ApiClientBuilder {
pub fn new(prefix: &str) -> Self {
Self {
prefix: prefix.to_owned(),
authentication: Authentication::default(),
user_agent: None,
}
}
pub fn authentication(&mut self, auth: Authentication) -> &mut Self {
self.authentication = auth;
self
}
pub fn user_agent(&mut self, user_agent: &str) -> &mut Self {
self.user_agent = Some(user_agent.to_owned());
self
}
pub fn build(&self) -> Result<ApiClient> {
Client::builder()
.user_agent(
self.user_agent
.clone()
.unwrap_or(DEFAULT_USER_AGENT.to_owned()),
)
.build()
.map(|client| ApiClient {
authentication: self.authentication.clone(),
client,
prefix: self.prefix.clone(),
})
}
}
#[derive(Clone, Default)]
pub enum Authentication {
Basic(BasicAuthentication),
Bearer(Option<String>),
#[default]
None,
}
impl Authentication {
pub fn new_basic(username: &str, password: &str) -> Self {
Authentication::Basic(BasicAuthentication::new(username, password))
}
pub fn new_bearer(token: &str) -> Self {
Authentication::Bearer(Some(token.to_owned()))
}
}
#[derive(Clone)]
pub struct BasicAuthentication {
username: String,
password: String,
}
impl BasicAuthentication {
pub fn new<S: Into<String>>(username: S, password: S) -> Self {
Self {
username: username.into(),
password: password.into(),
}
}
}
impl ApiClient {
fn create_request<T>(
&self,
uri: &str,
f: impl Fn(&Client, String) -> RequestBuilder,
t: Option<T>,
) -> RequestBuilder
where
T: Serialize,
{
let mut builder = f(&self.client, self.uri(uri));
builder = match &self.authentication {
Authentication::Basic(basic) => {
builder.basic_auth(basic.username.clone(), Some(basic.password.clone()))
}
Authentication::Bearer(token) => match token {
Some(t) => builder.bearer_auth(t.clone()),
None => builder,
},
Authentication::None => builder,
};
if let Some(object) = t {
builder = builder.json(&object)
}
builder
}
fn uri(&self, uri: &str) -> String {
format!("{}{}", self.prefix, uri)
}
pub async fn delete(&self, uri: &str) -> Result<()> {
self.create_request::<()>(uri, Client::delete, None)
.send()
.and_then(|r| ready(r.error_for_status()).map_ok(|_| ()))
.await
}
pub async fn get<R>(&self, uri: &str) -> Result<R>
where
R: DeserializeOwned,
{
self.create_request::<()>(uri, Client::get, None)
.send()
.and_then(Response::json::<R>)
.await
}
pub async fn post<T, R>(&self, uri: &str, object: T) -> Result<R>
where
T: Serialize,
R: DeserializeOwned,
{
self.create_request::<T>(uri, Client::post, Some(object))
.send()
.and_then(Response::json::<R>)
.await
}
pub async fn token_request<T>(&mut self, uri: &str, signature: &str, object: T) -> Result<()>
where
T: Serialize,
{
let token = self
.create_request::<T>(uri, Client::post, Some(object))
.header("Signature", signature)
.send()
.and_then(Response::text)
.await?;
self.authentication = Authentication::Bearer(Some(token));
Ok(())
}
pub async fn put<T, R>(&self, uri: &str, object: T) -> Result<R>
where
T: Serialize,
R: DeserializeOwned,
{
self.create_request::<T>(uri, Client::put, Some(object))
.send()
.and_then(Response::json::<R>)
.await
}
}