sentd 1.0.0

Official Rust SDK for the SENTD Email API
Documentation
use reqwest::{Client, Response};
use serde::{de::DeserializeOwned, Serialize};

use crate::error::Error;
use crate::emails::Emails;
use crate::batch::Batch;
use crate::templates::Templates;
use crate::domains::Domains;
use crate::webhooks::Webhooks;
use crate::analytics::Analytics;

const DEFAULT_BASE_URL: &str = "https://api.sentd.io";
const VERSION: &str = env!("CARGO_PKG_VERSION");

/// SENTD API Client
#[derive(Clone)]
pub struct Sentd {
    api_key: String,
    base_url: String,
    client: Client,
}

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

    /// Create a new SENTD 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(),
            client: Client::new(),
        }
    }

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

    /// Get the Batch API
    pub fn batch(&self) -> Batch {
        Batch::new(self.clone())
    }

    /// Get the Templates API
    pub fn templates(&self) -> Templates {
        Templates::new(self.clone())
    }

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

    /// Get the Webhooks API
    pub fn webhooks(&self) -> Webhooks {
        Webhooks::new(self.clone())
    }

    /// Get the Analytics API
    pub fn analytics(&self) -> Analytics {
        Analytics::new(self.clone())
    }

    /// Make a GET request
    pub(crate) async fn get<T: DeserializeOwned>(&self, path: &str) -> Result<T, Error> {
        let response = self.client
            .get(format!("{}{}", self.base_url, path))
            .header("Authorization", format!("Bearer {}", self.api_key))
            .header("User-Agent", format!("sentd-rust/{}", VERSION))
            .send()
            .await?;

        self.handle_response(response).await
    }

    /// Make a POST request
    pub(crate) async fn post<T: DeserializeOwned, B: Serialize>(&self, path: &str, body: &B) -> Result<T, Error> {
        let response = self.client
            .post(format!("{}{}", self.base_url, path))
            .header("Authorization", format!("Bearer {}", self.api_key))
            .header("Content-Type", "application/json")
            .header("User-Agent", format!("sentd-rust/{}", VERSION))
            .json(body)
            .send()
            .await?;

        self.handle_response(response).await
    }

    /// Make a PUT request
    pub(crate) async fn put<T: DeserializeOwned, B: Serialize>(&self, path: &str, body: &B) -> Result<T, Error> {
        let response = self.client
            .put(format!("{}{}", self.base_url, path))
            .header("Authorization", format!("Bearer {}", self.api_key))
            .header("Content-Type", "application/json")
            .header("User-Agent", format!("sentd-rust/{}", VERSION))
            .json(body)
            .send()
            .await?;

        self.handle_response(response).await
    }

    /// Make a PATCH request
    pub(crate) async fn patch<T: DeserializeOwned, B: Serialize>(&self, path: &str, body: &B) -> Result<T, Error> {
        let response = self.client
            .patch(format!("{}{}", self.base_url, path))
            .header("Authorization", format!("Bearer {}", self.api_key))
            .header("Content-Type", "application/json")
            .header("User-Agent", format!("sentd-rust/{}", VERSION))
            .json(body)
            .send()
            .await?;

        self.handle_response(response).await
    }

    /// Make a DELETE request
    pub(crate) async fn delete<T: DeserializeOwned>(&self, path: &str) -> Result<T, Error> {
        let response = self.client
            .delete(format!("{}{}", self.base_url, path))
            .header("Authorization", format!("Bearer {}", self.api_key))
            .header("User-Agent", format!("sentd-rust/{}", VERSION))
            .send()
            .await?;

        self.handle_response(response).await
    }

    async fn handle_response<T: DeserializeOwned>(&self, response: Response) -> Result<T, Error> {
        let status = response.status().as_u16();

        if status >= 400 {
            let body: serde_json::Value = response.json().await.unwrap_or_default();
            let message = body["error"]
                .as_str()
                .or(body["message"].as_str())
                .unwrap_or("Unknown error")
                .to_string();

            return Err(Error::from_status(status, message));
        }

        let data = response.json().await?;
        Ok(data)
    }
}