use crate::builder::ApiClientBuilder;
use anyhow::Result;
use bytes::Bytes;
use fiberplane_models::paging::{PagedVec, HAS_MORE_RESULTS_KEY, TOTAL_RESULTS_KEY};
use reqwest::{Client, Method, RequestBuilder, Response, StatusCode};
use thiserror::Error;
use url::Url;
#[derive(Debug, Error)]
pub enum ApiClientError<T> {
#[error("An invalid URL was provided: {0}")]
ParseError(#[from] url::ParseError),
#[error("An error occurred while making the request: {0}")]
ClientError(#[from] reqwest::Error),
#[error(transparent)]
ServiceError(T),
#[error("API returned an unknown response: Status: {0}, Body: {1:?}")]
InvalidResponse(StatusCode, Bytes),
}
#[derive(Debug)]
pub struct ApiClient {
pub client: Client,
pub server: Url,
}
impl ApiClient {
pub fn request(
&self,
method: Method,
endpoint: &str,
) -> Result<RequestBuilder, url::ParseError> {
let url = self.server.join(endpoint)?;
Ok(self.client.request(method, url))
}
pub fn builder(base_url: Url) -> ApiClientBuilder {
ApiClientBuilder::new(base_url)
}
pub async fn do_req_paged<T, E>(
&self,
req: RequestBuilder,
) -> Result<PagedVec<T>, ApiClientError<E>>
where
T: serde::de::DeserializeOwned,
E: serde::de::DeserializeOwned,
{
let response = req.send().await?;
let status_code = response.status();
let has_more_results = Self::parse_has_more_results_header(&response);
let total_results = Self::parse_total_results_header(&response);
let body = response.bytes().await?;
if let Ok(result) = serde_json::from_slice::<Vec<T>>(&body) {
let result = PagedVec {
inner: result,
has_more_results,
total_results,
};
return Ok(result);
}
if let Ok(result) = serde_json::from_slice::<E>(&body) {
return Err(ApiClientError::ServiceError(result));
}
Err(ApiClientError::InvalidResponse(status_code, body))
}
pub async fn do_req<T, E>(&self, req: RequestBuilder) -> Result<T, ApiClientError<E>>
where
T: serde::de::DeserializeOwned,
E: serde::de::DeserializeOwned,
{
let response = req.send().await?;
let status_code = response.status();
let body = response.bytes().await?;
if let Ok(result) = serde_json::from_slice::<T>(&body) {
return Ok(result);
}
if let Ok(result) = serde_json::from_slice::<E>(&body) {
return Err(ApiClientError::ServiceError(result));
}
Err(ApiClientError::InvalidResponse(status_code, body))
}
fn parse_has_more_results_header(response: &Response) -> bool {
response
.headers()
.get(HAS_MORE_RESULTS_KEY)
.map_or(false, |value| {
value
.to_str()
.map(|value| value.parse().unwrap_or_default())
.unwrap_or_default()
})
}
fn parse_total_results_header(response: &Response) -> Option<u32> {
response
.headers()
.get(TOTAL_RESULTS_KEY)
.map(|value| value.to_str().ok().and_then(|value| value.parse().ok()))
.unwrap_or_default()
}
}