mod bundle;
mod comment;
mod post;
mod showcase;
mod subscription_level;
mod target;
mod user;
use std::fmt::Display;
use crate::auth_provider::{AuthProvider, TokenPair};
use crate::error::{ApiError, ResultApi, ResultAuth};
use reqwest::header::{ACCEPT, CACHE_CONTROL, HeaderMap, HeaderValue, USER_AGENT};
use reqwest::{Client, Response, multipart};
#[derive(Default)]
pub(crate) struct QueryParams(Vec<(String, String)>);
impl QueryParams {
pub fn new() -> Self {
Self::default()
}
pub fn push(mut self, key: &str, value: Option<impl Display>) -> Self {
if let Some(v) = value {
self.0.push((key.to_string(), v.to_string()));
}
self
}
pub fn as_slice(&self) -> Vec<(&str, &str)> {
self.0
.iter()
.map(|(k, v)| (k.as_str(), v.as_str()))
.collect()
}
}
const DEFAULT_PAGE_SIZE: usize = 20;
#[derive(Debug, Clone)]
pub struct ApiClient {
base_url: String,
client: Client,
headers: HeaderMap,
auth_provider: AuthProvider,
}
impl ApiClient {
pub fn new(client: Client, base_url: impl Into<String> + Clone) -> Self {
let base_url = base_url.into();
let headers = Self::prepare_headers();
let auth_provider = AuthProvider::new(client.clone(), base_url.clone());
Self {
base_url,
client,
headers,
auth_provider,
}
}
fn prepare_headers() -> HeaderMap {
let mut headers = HeaderMap::new();
headers.insert(ACCEPT, HeaderValue::from_static("application/json"));
headers.insert(
USER_AGENT,
HeaderValue::from_static("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36"),
);
headers.insert(CACHE_CONTROL, HeaderValue::from_static("no-cache"));
headers.insert("DNT", HeaderValue::from_static("1"));
headers
}
pub async fn set_bearer_token(&self, access_token: &str) -> ResultAuth<()> {
self.auth_provider
.set_access_token_only(access_token.to_string())
.await
}
pub async fn set_refresh_token_and_device_id(
&self,
refresh_token: &str,
device_id: &str,
) -> ResultAuth<()> {
self.auth_provider
.set_refresh_token_and_device_id(refresh_token.to_string(), device_id.to_string())
.await
}
pub async fn clear_refresh_and_device_id(&self) {
self.auth_provider.clear_refresh_and_device_id().await
}
pub async fn clear_access_token(&self) {
self.auth_provider.clear_access_token().await
}
pub async fn refresh_tokens(&self) -> ResultAuth<TokenPair> {
self.auth_provider.refresh_tokens().await
}
pub fn headers_as_map(&self) -> std::collections::HashMap<String, String> {
self.headers
.iter()
.filter_map(|(k, v)| {
v.to_str()
.ok()
.map(|value| (k.to_string(), value.to_string()))
})
.collect()
}
async fn auth_headers(&self) -> ResultApi<HeaderMap> {
let mut headers = self.headers.clone();
self.auth_provider.apply_auth_header(&mut headers).await?;
Ok(headers)
}
fn url(&self, path: &str) -> String {
format!("{}/v1/{}", self.base_url, path)
}
async fn send_request(
&self,
path: &str,
builder: reqwest::RequestBuilder,
) -> ResultApi<Response> {
let response = builder.send().await.map_err(ApiError::HttpRequest)?;
self.handle_response(path, response).await
}
pub(crate) async fn get_json<T: serde::de::DeserializeOwned>(
&self,
path: &str,
query: &[(&str, &str)],
) -> ResultApi<T> {
let headers = self.auth_headers().await?;
let mut builder = self.client.get(self.url(path)).headers(headers);
if !query.is_empty() {
builder = builder.query(query);
}
let response = self.send_request(path, builder).await?;
self.parse_json(response).await
}
pub(crate) async fn post_form_json<
B: serde::Serialize + ?Sized,
T: serde::de::DeserializeOwned,
>(
&self,
path: &str,
body: &B,
) -> ResultApi<T> {
let headers = self.auth_headers().await?;
let builder = self.client.post(self.url(path)).headers(headers).form(body);
let response = self.send_request(path, builder).await?;
self.parse_json(response).await
}
#[allow(dead_code)]
pub(crate) async fn post_json_json<
B: serde::Serialize + ?Sized,
T: serde::de::DeserializeOwned,
>(
&self,
path: &str,
body: &B,
) -> ResultApi<T> {
let headers = self.auth_headers().await?;
let builder = self.client.post(self.url(path)).headers(headers).json(body);
let response = self.send_request(path, builder).await?;
self.parse_json(response).await
}
pub(crate) async fn post_multipart_json<T: serde::de::DeserializeOwned>(
&self,
path: &str,
form: multipart::Form,
) -> ResultApi<T> {
let mut headers = self.auth_headers().await?;
headers.remove("Content-Type");
let builder = self
.client
.post(self.url(path))
.headers(headers)
.multipart(form);
let response = self.send_request(path, builder).await?;
self.parse_json(response).await
}
pub(crate) async fn put_form_json<
B: serde::Serialize + ?Sized,
T: serde::de::DeserializeOwned,
>(
&self,
path: &str,
body: &B,
) -> ResultApi<T> {
let headers = self.auth_headers().await?;
let builder = self.client.put(self.url(path)).headers(headers).form(body);
let response = self.send_request(path, builder).await?;
self.parse_json(response).await
}
pub(crate) async fn put_form_ok<B: serde::Serialize + ?Sized>(
&self,
path: &str,
body: &B,
) -> ResultApi<()> {
let headers = self.auth_headers().await?;
let builder = self.client.put(self.url(path)).headers(headers).form(body);
self.send_request(path, builder).await?;
Ok(())
}
pub(crate) async fn delete_ok(&self, path: &str) -> ResultApi<()> {
let headers = self.auth_headers().await?;
let builder = self.client.delete(self.url(path)).headers(headers);
self.send_request(path, builder).await?;
Ok(())
}
}