use std::future::Future;
use async_trait::async_trait;
use reqwest::header::HeaderValue;
type AuthCallbackError = Box<dyn std::error::Error + Send + Sync>;
pub struct AuthCallback {
inner: Box<dyn LoginManagerConverted>,
}
impl AuthCallback {
#[must_use]
pub fn new<ACB>(auth_callback: ACB) -> Self
where
ACB: LoginManager + 'static,
{
Self { inner: Box::new(auth_callback) }
}
pub async fn authenticate(
&mut self,
client: reqwest::Client,
) -> Result<HeaderValue, AuthCallbackError> {
self.inner.authenticate(client).await
}
}
pub trait LoginManager: Send + Sync {
type Error: Into<AuthCallbackError>;
fn authenticate(
&mut self,
client: reqwest::Client,
) -> impl Future<Output = Result<HeaderValue, Self::Error>> + Send;
}
impl LoginManager for () {
type Error = AuthCallbackError;
async fn authenticate(&mut self, _client: reqwest::Client) -> Result<HeaderValue, Self::Error> {
unreachable!("Somehow called auth_callback of unit type `()`")
}
}
impl<F, Fut, E> LoginManager for F
where
F: FnMut(reqwest::Client) -> Fut + Send + Sync,
Fut: Future<Output = Result<HeaderValue, E>> + Send,
E: Into<AuthCallbackError>,
{
type Error = E;
async fn authenticate(&mut self, client: reqwest::Client) -> Result<HeaderValue, Self::Error> {
(self)(client).await
}
}
#[async_trait]
trait LoginManagerConverted: Send + Sync {
async fn authenticate(
&mut self,
client: reqwest::Client,
) -> Result<HeaderValue, AuthCallbackError>;
}
#[async_trait]
impl<T> LoginManagerConverted for T
where
T: LoginManager,
{
async fn authenticate(
&mut self,
client: reqwest::Client,
) -> Result<HeaderValue, AuthCallbackError> {
LoginManager::authenticate(self, client).await.map_err(Into::into)
}
}
#[cfg(test)]
mod tests {
#![allow(dead_code, reason = "Just check whether it compiles")]
use super::*;
async fn auth_test_fn(_client: reqwest::Client) -> anyhow::Result<HeaderValue> {
anyhow::bail!("Test");
}
fn construct_test_fn() -> AuthCallback {
AuthCallback::new(auth_test_fn)
}
struct MyLoginManager {
fail_after: usize,
}
impl LoginManager for MyLoginManager {
type Error = anyhow::Error;
async fn authenticate(
&mut self,
_client: reqwest::Client,
) -> Result<HeaderValue, Self::Error> {
if self.fail_after == 0 {
anyhow::bail!("test");
} else {
self.fail_after -= 1;
Ok(HeaderValue::from_static("whatever"))
}
}
}
fn construct_test_login_manager() -> AuthCallback {
AuthCallback::new(MyLoginManager { fail_after: 5 })
}
fn construct_test_closure() -> AuthCallback {
AuthCallback::new(async |client: reqwest::Client| {
let _response = client.get("invalid URL").send().await?;
anyhow::Ok(HeaderValue::from_static("ignored"))
})
}
}