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(¬ification.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}