use std::time::Duration;
use crate::error::{Error, Result};
use crate::http::{HttpClient, DEFAULT_BASE_URL, USER_AGENT};
use crate::resources::{
Accounts, Ads, Analytics, Automations, Inbox, Labels, Media, Posts, Webhooks, Workspaces,
};
pub const API_KEY_ENV: &str = "FOPOST_API_KEY";
const DEFAULT_TIMEOUT: Duration = Duration::from_secs(30);
const DEFAULT_MAX_RETRIES: u32 = 3;
#[derive(Debug, Clone)]
pub struct Client {
http: HttpClient,
}
impl Client {
pub fn new(api_key: impl Into<String>) -> Result<Self> {
Client::builder().api_key(api_key).build()
}
pub fn from_env() -> Result<Self> {
let api_key = std::env::var(API_KEY_ENV).map_err(|_| {
Error::Config(format!(
"fopost: {API_KEY_ENV} is not set — pass the key explicitly"
))
})?;
Client::new(api_key)
}
pub fn builder() -> ClientBuilder {
ClientBuilder::default()
}
pub fn base_url(&self) -> &str {
self.http.base_url()
}
pub fn api_key(&self) -> &str {
self.http.api_key()
}
pub fn posts(&self) -> Posts<'_> {
Posts { http: &self.http }
}
pub fn accounts(&self) -> Accounts<'_> {
Accounts { http: &self.http }
}
pub fn workspaces(&self) -> Workspaces<'_> {
Workspaces { http: &self.http }
}
pub fn labels(&self) -> Labels<'_> {
Labels { http: &self.http }
}
pub fn webhooks(&self) -> Webhooks<'_> {
Webhooks { http: &self.http }
}
pub fn automations(&self) -> Automations<'_> {
Automations { http: &self.http }
}
pub fn analytics(&self) -> Analytics<'_> {
Analytics { http: &self.http }
}
pub fn media(&self) -> Media<'_> {
Media { http: &self.http }
}
pub fn inbox(&self) -> Inbox<'_> {
Inbox { http: &self.http }
}
pub fn ads(&self) -> Ads<'_> {
Ads { http: &self.http }
}
pub async fn request<B: serde::Serialize + ?Sized>(
&self,
method: reqwest::Method,
path: &str,
query: Option<Vec<(&'static str, String)>>,
body: Option<&B>,
) -> Result<serde_json::Value> {
self.http.send_value(method, path, query, body).await
}
}
#[derive(Debug, Default)]
pub struct ClientBuilder {
api_key: Option<String>,
base_url: Option<String>,
timeout: Option<Duration>,
max_retries: Option<u32>,
http_client: Option<reqwest::Client>,
}
impl ClientBuilder {
pub fn api_key(mut self, api_key: impl Into<String>) -> Self {
self.api_key = Some(api_key.into());
self
}
pub fn base_url(mut self, base_url: impl Into<String>) -> Self {
self.base_url = Some(base_url.into());
self
}
pub fn timeout(mut self, timeout: Duration) -> Self {
self.timeout = Some(timeout);
self
}
pub fn max_retries(mut self, max_retries: u32) -> Self {
self.max_retries = Some(max_retries);
self
}
pub fn http_client(mut self, http_client: reqwest::Client) -> Self {
self.http_client = Some(http_client);
self
}
pub fn build(self) -> Result<Client> {
let api_key = self
.api_key
.filter(|key| !key.is_empty())
.ok_or_else(|| Error::Config("fopost: an api key is required".into()))?;
let http_client = match self.http_client {
Some(client) => client,
None => reqwest::Client::builder()
.timeout(self.timeout.unwrap_or(DEFAULT_TIMEOUT))
.user_agent(USER_AGENT)
.build()?,
};
let http = HttpClient::new(
http_client,
api_key,
self.base_url.as_deref().unwrap_or(DEFAULT_BASE_URL),
self.max_retries.unwrap_or(DEFAULT_MAX_RETRIES),
)?;
Ok(Client { http })
}
}