201 lines
6.5 KiB
Rust
201 lines
6.5 KiB
Rust
use super::{
|
|
Request,
|
|
error,
|
|
token,
|
|
auth,
|
|
};
|
|
use crate::utils::Bool;
|
|
|
|
#[async_trait::async_trait]
|
|
trait Interface {
|
|
fn client(&self) -> &reqwest::Client;
|
|
fn url(&self, endpoint: &str) -> Result<reqwest::Url, url::ParseError>;
|
|
fn current_token(&self) -> Option<std::sync::Arc<token::Token>>;
|
|
async fn refresh_token(&self, api: Api) -> Result<Option<std::sync::Arc<token::Token>>, error::Token>;
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
struct Internal<T: token::Provider> {
|
|
client: reqwest::Client,
|
|
base: reqwest::Url,
|
|
token_provider: T,
|
|
}
|
|
|
|
impl<T: token::Provider> Internal<T> {
|
|
fn new(client: reqwest::Client, base: reqwest::Url, token_provider: T)
|
|
-> std::sync::Arc<Self> {
|
|
std::sync::Arc::new(Self { client, base, token_provider })
|
|
}
|
|
|
|
#[tracing::instrument(skip_all, fields(endpoint=request.endpoint(), request, token))]
|
|
async fn call<R: Request + Sized>(&self, request: &R, token: Option<&token::Token>) -> Result<R::Response, error::Api>
|
|
{
|
|
let url = self.base.join(request.endpoint()).map_err(error::Api::Url)?;
|
|
let builder = self.client.request(R::METHOD, url);
|
|
let builder = if let Some(token) = token {
|
|
token.r#use(builder)
|
|
} else {
|
|
builder
|
|
};
|
|
|
|
let response = builder.json(request)
|
|
.send()
|
|
.await
|
|
.map_err(error::Api::Reqwest)?;
|
|
|
|
match response.status() {
|
|
reqwest::StatusCode::OK => (),
|
|
status => return Err(error::Api::Http(status)),
|
|
}
|
|
let result: R::Response = response.json().await?;
|
|
Ok(result)
|
|
}
|
|
}
|
|
|
|
async fn deserialize<R: Request>(response: reqwest::Response) -> Result<R::Response, error::Api>
|
|
{
|
|
let response: R::Response = response.json().await.map_err(error::Api::Reqwest)?;
|
|
Ok(response)
|
|
}
|
|
|
|
#[tracing::instrument(skip_all, fields(url, request, token))]
|
|
async fn call<R: Request>(client: &reqwest::Client, url: reqwest::Url, request: &R, token: Option<&token::Token>) -> Result<R::Response, error::Api> {
|
|
let builder = client.request(R::METHOD, url);
|
|
let builder = if let Some(token) = token {
|
|
builder.bearer_auth(token.as_ref())
|
|
} else {
|
|
builder
|
|
};
|
|
let response = builder.json(request)
|
|
.send()
|
|
.await
|
|
.map_err(error::Api::Reqwest)?;
|
|
|
|
match response.status() {
|
|
reqwest::StatusCode::OK =>
|
|
deserialize::<R>(response).await,
|
|
status => Err(error::Api::Http(status)),
|
|
}
|
|
}
|
|
|
|
#[async_trait::async_trait]
|
|
impl<T: token::Provider> Interface for Internal<T> {
|
|
fn client(&self) -> &reqwest::Client {
|
|
&self.client
|
|
}
|
|
|
|
fn url(&self, endpoint: &str) -> Result<reqwest::Url, url::ParseError> {
|
|
self.base.join(endpoint)
|
|
}
|
|
|
|
fn current_token(&self) -> Option<std::sync::Arc<token::Token>> {
|
|
self.token_provider.current()
|
|
}
|
|
|
|
async fn refresh_token(&self, api: Api) -> Result<Option<std::sync::Arc<token::Token>>, error::Token> {
|
|
self.token_provider.refresh(api).await
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
struct NoToken;
|
|
|
|
#[async_trait::async_trait]
|
|
impl token::Provider for NoToken {
|
|
fn current(&self) -> Option<std::sync::Arc<token::Token>> {
|
|
None
|
|
}
|
|
|
|
async fn refresh(&self, _api: Api) -> Result<Option<std::sync::Arc<token::Token>>, error::Token> {
|
|
Ok(None)
|
|
}
|
|
}
|
|
|
|
#[derive(Clone)]
|
|
pub struct Api(std::sync::Arc<dyn Interface + Sync + Send>);
|
|
|
|
impl Api {
|
|
pub async fn new<A: auth::Provider>(auth: A) -> Result<Self, error::Api>
|
|
{
|
|
Self::new_with_base("https://api.spond.com", auth).await
|
|
}
|
|
|
|
pub async fn new_with_base<A: auth::Provider>(base: &str, auth: A) -> Result<Self, error::Api> {
|
|
let base = reqwest::Url::parse(base)?;
|
|
let host = base.host_str().unwrap().to_owned();
|
|
|
|
let client = reqwest::Client::builder()
|
|
//.https_only(true)
|
|
.redirect(reqwest::redirect::Policy::limited(1))
|
|
.retry(reqwest::retry::for_host(host)
|
|
.max_retries_per_request(2)
|
|
.classify_fn(|req_rep| {
|
|
use reqwest::StatusCode;
|
|
match req_rep.status() {
|
|
Some(StatusCode::UNAUTHORIZED) => req_rep.retryable(),
|
|
_ => req_rep.success(),
|
|
}
|
|
}))
|
|
// TODO
|
|
//.cookie_store(true)
|
|
.user_agent("spond-selection-bot")
|
|
.read_timeout(std::time::Duration::from_secs(5))
|
|
.connect_timeout(std::time::Duration::from_secs(15))
|
|
.connection_verbose(true)
|
|
.build()?;
|
|
|
|
let api = Api(Internal::new(client.clone(), base.clone(), NoToken));
|
|
let provider = auth.authenticate(api).await?;
|
|
|
|
tracing::info!("{:?}", provider);
|
|
let api = Api(Internal::new(client, base, provider));
|
|
Ok(api)
|
|
}
|
|
|
|
|
|
pub async fn call<R: Request + Sized>(&self, request: &R) -> Result<R::Response, error::Api>
|
|
{
|
|
//async fn call<C: Call + ?Sized>(&self, api: &Api, call: &C, request: &C::Request) -> Result<C::Response, error::Call::<C>> {
|
|
// let mut token = self.token_provider.current();
|
|
// loop {
|
|
// match self.common.call(call, request, &token) {
|
|
// Err(error::Call::<C>::Http(reqwest::StatusCode::UNAUTHORIZED)) if token.is_some_and(|token|token.expired())
|
|
// => token = self.token_provider.refresh(api).map_err(error::Call::<C>::Token)?,
|
|
// result => return result,
|
|
// }
|
|
// }
|
|
//}
|
|
|
|
let client = self.0.client();
|
|
|
|
if R::Authorization::VALUE == false {
|
|
let url = self.0.url(request.endpoint())?;
|
|
call(client, url, request, None).await
|
|
} else {
|
|
let mut current = self.0.current_token();
|
|
loop {
|
|
use std::ops::Deref;
|
|
let token = if let Some(ref token) = current {
|
|
Some(token.deref())
|
|
} else {
|
|
None
|
|
};
|
|
let url = self.0.url(request.endpoint())?;
|
|
current = match call(client, url, request, token).await {
|
|
Err(error::Api::Http(reqwest::StatusCode::UNAUTHORIZED)) if current.is_some_and(|t|t.expired())
|
|
=> self.0.refresh_token(self.clone()).await?,
|
|
result => break result,
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod test {
|
|
use super::*;
|
|
|
|
#[tokio::test]
|
|
async fn mock() {}
|
|
}
|
|
|