Skip to main content

notifica_rust_sdk/
client.rs

1use crate::{
2    error::{NotificaError, NotificaResult},
3    payload::Notification,
4};
5
6/// Async HTTP client for the Notifica webhook API.
7///
8/// Create one instance per application (it holds an internal connection pool)
9/// and reuse it for all notifications.
10///
11/// # Example
12/// ```rust,no_run
13/// use notifica_crate::{EmailPayload, Notification, NotificaClient};
14///
15/// #[tokio::main]
16/// async fn main() -> Result<(), Box<dyn std::error::Error>> {
17///     let client = NotificaClient::new("http://localhost:8000", "my-tenant");
18///
19///     client.send(
20///         "user_registered",
21///         Notification::new()
22///             .email(EmailPayload::new("alice@example.com").subject("Welcome!")),
23///     ).await?;
24///
25///     Ok(())
26/// }
27/// ```
28pub struct NotificaClient {
29    base_url:  String,
30    tenant_id: String,
31    http:      reqwest::Client,
32}
33
34impl NotificaClient {
35    /// Create a client with default [`reqwest::Client`] settings.
36    ///
37    /// Panics if `base_url` or `tenant_id` is empty.
38    pub fn new(base_url: impl Into<String>, tenant_id: impl Into<String>) -> Self {
39        let base_url = base_url.into();
40        let tenant_id = tenant_id.into();
41        assert!(!base_url.is_empty(), "base_url must not be empty");
42        assert!(!tenant_id.is_empty(), "tenant_id must not be empty");
43        Self {
44            base_url,
45            tenant_id,
46            http: reqwest::Client::new(),
47        }
48    }
49
50    /// Create a client with a custom [`reqwest::Client`].
51    ///
52    /// Use this when you need custom timeouts, TLS settings, or middleware.
53    pub fn with_http_client(
54        base_url:  impl Into<String>,
55        tenant_id: impl Into<String>,
56        http:      reqwest::Client,
57    ) -> Self {
58        Self {
59            base_url:  base_url.into(),
60            tenant_id: tenant_id.into(),
61            http,
62        }
63    }
64
65    /// Send a notification to the given event name.
66    ///
67    /// Calls `POST {base_url}/webhook/{tenant_id}/{event_name}` with the
68    /// serialized [`Notification`] as the JSON body.
69    ///
70    /// Returns `Ok(())` on a `200` response. Any other status code is
71    /// treated as [`NotificaError::UnexpectedResponse`].
72    pub async fn send(
73        &self,
74        event_name:   impl Into<String>,
75        notification: Notification,
76    ) -> NotificaResult<()> {
77        let url = format!(
78            "{}/webhook/{}/{}",
79            self.base_url.trim_end_matches('/'),
80            self.tenant_id,
81            event_name.into(),
82        );
83
84        let response = self
85            .http
86            .post(&url)
87            .json(&notification.into_payload())
88            .send()
89            .await?;
90
91        let status = response.status();
92        if status.is_success() {
93            return Ok(());
94        }
95
96        let body = response.text().await.unwrap_or_default();
97        Err(NotificaError::UnexpectedResponse {
98            status: status.as_u16(),
99            body,
100        })
101    }
102}