unosend 1.0.0

Official Rust SDK for Unosend - Email API for developers
Documentation
use crate::audiences::AudiencesApi;
use crate::contacts::ContactsApi;
use crate::domains::DomainsApi;
use crate::emails::EmailsApi;
use crate::error::UnosendError;
use reqwest::Client;
use serde::de::DeserializeOwned;
use serde::Serialize;

const DEFAULT_BASE_URL: &str = "https://api.unosend.co/v1";

/// Main client for the Unosend API.
#[derive(Clone)]
pub struct Unosend {
    api_key: String,
    base_url: String,
    http_client: Client,
}

impl Unosend {
    /// Create a new Unosend client with the given API key.
    pub fn new(api_key: impl Into<String>) -> Self {
        Self::with_base_url(api_key, DEFAULT_BASE_URL)
    }

    /// Create a new Unosend client with a custom base URL.
    pub fn with_base_url(api_key: impl Into<String>, base_url: impl Into<String>) -> Self {
        Self {
            api_key: api_key.into(),
            base_url: base_url.into(),
            http_client: Client::new(),
        }
    }

    /// Get the Emails API.
    pub fn emails(&self) -> EmailsApi {
        EmailsApi::new(self.clone())
    }

    /// Get the Domains API.
    pub fn domains(&self) -> DomainsApi {
        DomainsApi::new(self.clone())
    }

    /// Get the Audiences API.
    pub fn audiences(&self) -> AudiencesApi {
        AudiencesApi::new(self.clone())
    }

    /// Get the Contacts API.
    pub fn contacts(&self) -> ContactsApi {
        ContactsApi::new(self.clone())
    }

    pub(crate) async fn post<T, R>(&self, path: &str, body: &T) -> Result<R, UnosendError>
    where
        T: Serialize,
        R: DeserializeOwned,
    {
        let url = format!("{}{}", self.base_url, path);
        let response = self
            .http_client
            .post(&url)
            .header("Authorization", format!("Bearer {}", self.api_key))
            .header("Content-Type", "application/json")
            .header("User-Agent", "unosend-rust/1.0.0")
            .json(body)
            .send()
            .await?;

        self.handle_response(response).await
    }

    pub(crate) async fn get<R>(&self, path: &str) -> Result<R, UnosendError>
    where
        R: DeserializeOwned,
    {
        let url = format!("{}{}", self.base_url, path);
        let response = self
            .http_client
            .get(&url)
            .header("Authorization", format!("Bearer {}", self.api_key))
            .header("User-Agent", "unosend-rust/1.0.0")
            .send()
            .await?;

        self.handle_response(response).await
    }

    pub(crate) async fn delete(&self, path: &str) -> Result<(), UnosendError> {
        let url = format!("{}{}", self.base_url, path);
        let response = self
            .http_client
            .delete(&url)
            .header("Authorization", format!("Bearer {}", self.api_key))
            .header("User-Agent", "unosend-rust/1.0.0")
            .send()
            .await?;

        if !response.status().is_success() {
            let status = response.status().as_u16();
            let text = response.text().await.unwrap_or_default();
            return Err(UnosendError::Api {
                message: text,
                status_code: status,
            });
        }

        Ok(())
    }

    async fn handle_response<R>(&self, response: reqwest::Response) -> Result<R, UnosendError>
    where
        R: DeserializeOwned,
    {
        let status = response.status();
        let text = response.text().await?;

        if !status.is_success() {
            return Err(UnosendError::Api {
                message: text,
                status_code: status.as_u16(),
            });
        }

        serde_json::from_str(&text).map_err(|e| UnosendError::Parse(e.to_string()))
    }
}