use std::{
future::Future,
sync::{Arc, RwLock},
};
use tonic::{
metadata::{Ascii, MetadataValue},
Code,
};
use crate::{
client::AuthToken, error::Result, intercept::InterceptedChannel, lock::RwLockExt, AuthClient,
Error::GRpcStatus,
};
#[derive(Clone)]
pub struct CallOptions {
pub creds: Arc<RwLock<Option<(String, String)>>>,
pub refresh_expired_token: bool,
}
#[derive(Clone)]
pub struct ClientCallerBuilder {
options: CallOptions,
auth_token: AuthToken,
auth_client: AuthClient,
channel: InterceptedChannel,
}
impl ClientCallerBuilder {
pub fn new(
options: CallOptions,
auth_token: AuthToken,
auth_client: AuthClient,
channel: InterceptedChannel,
) -> Self {
Self {
options,
auth_token,
auth_client,
channel,
}
}
pub fn build<T>(self, f_inner: impl FnOnce(InterceptedChannel) -> T) -> ClientCaller<T> {
ClientCaller::new(
f_inner(self.channel),
self.auth_client,
self.auth_token,
self.options,
)
}
}
#[derive(Clone)]
pub struct ClientCaller<T> {
inner: T,
auth_client: AuthClient,
auth_token: AuthToken,
options: CallOptions,
}
impl<T> ClientCaller<T> {
pub fn new(
inner: T,
auth_client: AuthClient,
auth_token: AuthToken,
options: CallOptions,
) -> Self {
Self {
inner,
auth_client,
auth_token,
options,
}
}
pub async fn refresh_token(&self) -> Result<()> {
let creds = self.options.creds.read_unpoisoned().clone();
if let Some((user, password)) = creds {
let token = self.do_authenticate(user, password).await?;
self.auth_token.write_unpoisoned().replace(token);
} else {
let _ = self.auth_token.write_unpoisoned().take();
}
Ok(())
}
pub async fn update_user(&mut self, creds: Option<(String, String)>) -> Result<()> {
if let Some((ref name, ref password)) = creds {
let token = self.do_authenticate(name.clone(), password.clone()).await?;
self.auth_token.write_unpoisoned().replace(token);
} else {
let _ = self.auth_token.write_unpoisoned().take();
}
*self.options.creds.write_unpoisoned() = creds;
Ok(())
}
pub fn with(mut self, f: impl FnOnce(T) -> T) -> Self {
self.inner = f(self.inner);
self
}
pub async fn do_call<Req, C, Ret>(&mut self, req: Req, call: C) -> Result<Ret>
where
for<'a> C: ClientCall<&'a mut T, Req, Output = Result<Ret>>,
Req: Clone,
{
let has_creds = self.options.creds.read_unpoisoned().is_some();
if !self.options.refresh_expired_token || !has_creds {
return (call)(&mut self.inner, req).await;
}
let resp = (call)(&mut self.inner, req.clone()).await;
match resp {
Err(GRpcStatus(status)) if status.code() == Code::Unauthenticated => {
self.refresh_token().await?;
(call)(&mut self.inner, req).await
}
res => res,
}
}
async fn do_authenticate(
&self,
user: String,
password: String,
) -> Result<MetadataValue<Ascii>> {
let resp = self
.auth_client
.clone()
.authenticate(user, password)
.await?;
let token = resp.token().parse()?;
Ok(token)
}
}
pub trait ClientCall<Client, Req>: Fn(Client, Req) -> Self::OutputFuture {
type Output;
type OutputFuture: Future<Output = <Self as ClientCall<Client, Req>>::Output>;
}
impl<F, Fut, Client, Req> ClientCall<Client, Req> for F
where
F: Fn(Client, Req) -> Fut,
Fut: Future,
{
type OutputFuture = Fut;
type Output = Fut::Output;
}