use crate::dsh_api_tenant::DshApiTenant;
use crate::dsh_jwt::DshJwt;
use crate::error::DshApiResult;
use crate::platform::DshPlatform;
use crate::token_fetcher::TokenFetcher;
use crate::{DshApiError, OPENAPI_SPEC};
use log::{debug, trace};
use reqwest::{Client, Error as ReqwestError, Response, StatusCode};
use serde::de::DeserializeOwned;
use std::fmt::Debug;
use std::str::{from_utf8, FromStr};
pub struct DshApiClient {
static_token: Option<String>,
token_fetcher: Option<TokenFetcher>,
pub(crate) client: Client,
tenant: DshApiTenant,
}
impl DshApiClient {
pub fn openapi_spec() -> &'static str {
OPENAPI_SPEC
}
pub fn has_static_token(&self) -> bool {
self.static_token.is_some()
}
pub fn static_token(&self) -> &Option<String> {
&self.static_token
}
pub fn token_fetcher(&self) -> &Option<TokenFetcher> {
&self.token_fetcher
}
pub fn has_token_fetcher(&self) -> bool {
self.token_fetcher.is_some()
}
pub fn tenant(&self) -> &DshApiTenant {
&self.tenant
}
pub fn tenant_name(&self) -> &str {
self.tenant.name()
}
pub fn platform(&self) -> &DshPlatform {
self.tenant.platform()
}
pub async fn bearer_token(&self) -> DshApiResult<String> {
if let Some(static_token) = &self.static_token {
Ok(format!("Bearer {}", static_token))
} else if let Some(token_fetcher) = &self.token_fetcher {
token_fetcher.get_bearer_token().await
} else {
Err(DshApiError::configuration("either a static token or a token fetcher must be provided"))
}
}
pub async fn fresh_bearer_token(&self) -> DshApiResult<String> {
if let Some(static_token) = &self.static_token {
Ok(format!("Bearer {}", static_token))
} else if let Some(token_fetcher) = &self.token_fetcher {
token_fetcher.get_fresh_bearer_token().await
} else {
Err(DshApiError::configuration("either a static token or a token fetcher must be provided"))
}
}
pub async fn raw_token(&self) -> DshApiResult<String> {
if let Some(static_token) = &self.static_token {
Ok(static_token.clone())
} else if let Some(token_fetcher) = &self.token_fetcher {
token_fetcher.get_raw_token().await
} else {
Err(DshApiError::configuration("either a static token or a token fetcher must be provided"))
}
}
pub async fn fresh_raw_token(&self) -> DshApiResult<String> {
if let Some(static_token) = &self.static_token {
Ok(static_token.clone())
} else if let Some(token_fetcher) = &self.token_fetcher {
token_fetcher.get_fresh_raw_token().await
} else {
Err(DshApiError::configuration("either a static token or a token fetcher must be provided"))
}
}
pub async fn jwt(&self) -> DshApiResult<DshJwt> {
if let Some(static_token) = &self.static_token {
DshJwt::from_str(static_token).map_err(|_| DshApiError::unexpected("could not parse static jwt token".to_string()))
} else if let Some(token_fetcher) = &self.token_fetcher {
token_fetcher.get_jwt().await
} else {
Err(DshApiError::configuration("either a static token or a token fetcher must be provided"))
}
}
pub async fn fresh_jwt(&self) -> DshApiResult<DshJwt> {
if let Some(static_token) = &self.static_token {
DshJwt::from_str(static_token).map_err(|_| DshApiError::unexpected("could not parse static jwt token"))
} else if let Some(token_fetcher) = &self.token_fetcher {
token_fetcher.get_fresh_jwt().await
} else {
Err(DshApiError::configuration("either a static token or a token fetcher must be provided"))
}
}
pub(crate) fn with_static_token(static_token: String, client: Option<Client>, tenant: DshApiTenant) -> Self {
match client {
Some(client) => {
debug!("create dsh api client from static token");
Self { static_token: Some(static_token), token_fetcher: None, client, tenant }
}
None => {
debug!("create dsh api client from static token with default https client");
Self { static_token: Some(static_token), token_fetcher: None, client: Client::new(), tenant }
}
}
}
pub(crate) fn with_token_fetcher(token_fetcher: TokenFetcher, client: Option<Client>, tenant: DshApiTenant) -> Self {
match client {
Some(client) => {
debug!("create dsh api client from token fetcher");
Self { static_token: None, token_fetcher: Some(token_fetcher), client, tenant }
}
None => {
debug!("create dsh api client from token fetcher with default https client");
Self { static_token: None, token_fetcher: Some(token_fetcher), client: Client::new(), tenant }
}
}
}
#[allow(dead_code)]
pub(crate) async fn process_delete(&self, reqwest_response: Result<Response, ReqwestError>) -> DshApiResult<()> {
self.process_no_content(reqwest_response).await
}
#[allow(dead_code)]
pub(crate) async fn process_get_deserializable<T>(&self, reqwest_response: Result<Response, ReqwestError>) -> DshApiResult<T>
where
T: Debug + DeserializeOwned,
{
match reqwest_response {
Ok(response) => match response.status() {
StatusCode::OK => response.json::<T>().await.map_err(DshApiError::from),
status_code => Self::process_errors(status_code, response).await,
},
Err(reqwest_error) => Err(DshApiError::from(reqwest_error)),
}
}
pub(crate) async fn process_get_string(&self, reqwest_response: Result<Response, ReqwestError>) -> DshApiResult<String> {
match reqwest_response {
Ok(response) => match response.status() {
StatusCode::OK => Ok(from_utf8(response.bytes().await?.as_ref())?.to_string()),
status_code => Self::process_errors(status_code, response).await,
},
Err(response_error) => Err(DshApiError::from(response_error)),
}
}
#[allow(dead_code)]
pub(crate) async fn process_head(&self, reqwest_response: Result<Response, ReqwestError>) -> DshApiResult<()> {
self.process_no_content(reqwest_response).await
}
#[allow(dead_code)]
pub(crate) async fn process_patch(&self, reqwest_response: Result<Response, ReqwestError>) -> DshApiResult<()> {
self.process_no_content(reqwest_response).await
}
#[allow(dead_code)]
pub(crate) async fn process_post(&self, reqwest_response: Result<Response, ReqwestError>) -> DshApiResult<()> {
self.process_no_content(reqwest_response).await
}
#[allow(dead_code)]
pub(crate) async fn process_post_deserializable<T>(&self, reqwest_response: Result<Response, ReqwestError>) -> DshApiResult<T>
where
T: DeserializeOwned,
{
match reqwest_response {
Ok(response) => match response.status() {
StatusCode::OK | StatusCode::CREATED | StatusCode::ACCEPTED => response.json::<T>().await.map_err(DshApiError::from),
status_code => Self::process_errors(status_code, response).await,
},
Err(reqwest_error) => Err(DshApiError::from(reqwest_error)),
}
}
#[allow(dead_code)]
pub(crate) async fn process_put(&self, reqwest_response: Result<Response, ReqwestError>) -> DshApiResult<()> {
self.process_no_content(reqwest_response).await
}
async fn process_no_content(&self, reqwest_response: Result<Response, ReqwestError>) -> DshApiResult<()> {
match reqwest_response {
Ok(response) => match response.status() {
StatusCode::OK | StatusCode::CREATED | StatusCode::ACCEPTED | StatusCode::NO_CONTENT => Self::trace_response_string(response.status(), response).await,
status_code => Self::process_errors(status_code, response).await,
},
Err(reqwest_error) => Err(DshApiError::from(reqwest_error)),
}
}
async fn trace_response_string(status_code: StatusCode, response: Response) -> DshApiResult<()> {
if let Some(response_string) = Self::get_response_string(response).await {
trace!("{} -> response string: {}", status_code, response_string);
}
Ok(())
}
async fn process_errors<T>(status_code: StatusCode, response: Response) -> DshApiResult<T> {
match status_code {
StatusCode::BAD_REQUEST => Err(DshApiError::BadRequest { message: Self::get_response_string(response).await }),
StatusCode::UNAUTHORIZED => Err(DshApiError::NotAuthorized { message: Self::get_response_string(response).await }),
StatusCode::NOT_FOUND => Err(DshApiError::NotFound { message: Self::get_response_string(response).await }),
unexpected_status_code => {
Err(DshApiError::Unexpected { message: format!("unexpected status code: {}", unexpected_status_code), cause: Self::get_response_string(response).await })
}
}
}
async fn get_response_string(response: Response) -> Option<String> {
response
.bytes()
.await
.ok()
.and_then(|response_bytes| from_utf8(response_bytes.as_ref()).map(|response_str| response_str.to_string()).ok())
.and_then(|response_string| if response_string.is_empty() { None } else { Some(response_string) })
}
}
#[test]
fn test_dsh_api_client_is_send() {
fn assert_send<T: Send>() {}
assert_send::<DshApiClient>();
}
#[test]
fn test_dsh_api_client_is_sync() {
fn assert_sync<T: Sync>() {}
assert_sync::<DshApiClient>();
}