Skip to main content

nautilus_rs/resources/relay/
mod.rs

1pub mod types;
2
3use std::sync::Arc;
4
5use crate::{error::Error, http::HttpClient, types::Paginated};
6use types::{ListMessagesParams, Message, SendMessageParams};
7
8/// Relay service client — Webhooks-as-a-Service.
9///
10/// Delivers JSON events to HTTP endpoints that have subscribed through the
11/// Verne dashboard or the Relay API.
12///
13/// Obtain a `Relay` instance either as part of the unified [`Verne`] client or
14/// standalone:
15///
16/// ```no_run
17/// // Standalone
18/// use nautilus_rs::Relay;
19/// let relay = Relay::new("vrn_relay_live_sk_…");
20///
21/// // Via unified client
22/// use nautilus_rs::Verne;
23/// # fn run() -> Result<(), nautilus_rs::Error> {
24/// let verne = Verne::builder().relay("vrn_relay_live_sk_…").build()?;
25/// let relay = verne.relay()?;
26/// # Ok(())
27/// # }
28/// ```
29///
30/// [`Verne`]: crate::Verne
31pub struct Relay {
32    http: Arc<HttpClient>,
33}
34
35impl std::fmt::Debug for Relay {
36    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
37        f.debug_struct("Relay").finish_non_exhaustive()
38    }
39}
40
41impl Relay {
42    /// Create a `Relay` client with default settings.
43    ///
44    /// Panics if the API key is empty or the HTTP client cannot be
45    /// initialised. Use [`Relay::builder`] for fallible construction.
46    pub fn new(api_key: impl Into<String>) -> Self {
47        Self::builder()
48            .api_key(api_key)
49            .build()
50            .expect("failed to build Relay client")
51    }
52
53    /// Return a [`RelayBuilder`] for fine-grained configuration.
54    pub fn builder() -> RelayBuilder {
55        RelayBuilder::default()
56    }
57
58    pub(crate) fn from_http(http: Arc<HttpClient>) -> Self {
59        Self { http }
60    }
61
62    /// Return a [`MessagesClient`] for sending and listing webhook messages.
63    pub fn messages(&self) -> MessagesClient {
64        MessagesClient {
65            http: Arc::clone(&self.http),
66        }
67    }
68}
69
70/// Builder for a standalone [`Relay`] client.
71///
72/// # Example
73///
74/// ```no_run
75/// use nautilus_rs::Relay;
76///
77/// let relay = Relay::builder()
78///     .api_key("vrn_relay_live_sk_…")
79///     .timeout_secs(15)
80///     .build()
81///     .expect("invalid configuration");
82/// ```
83#[derive(Default)]
84pub struct RelayBuilder {
85    api_key: Option<String>,
86    base_url: Option<String>,
87    timeout_secs: Option<u64>,
88}
89
90impl RelayBuilder {
91    /// Set the Relay API key (**required**).
92    pub fn api_key(mut self, key: impl Into<String>) -> Self {
93        self.api_key = Some(key.into());
94        self
95    }
96
97    /// Override the API base URL (default: `https://api.vernesoft.com`).
98    pub fn base_url(mut self, url: impl Into<String>) -> Self {
99        self.base_url = Some(url.into());
100        self
101    }
102
103    /// Set the HTTP request timeout in seconds (default: `30`).
104    pub fn timeout_secs(mut self, secs: u64) -> Self {
105        self.timeout_secs = Some(secs);
106        self
107    }
108
109    /// Consume the builder and return a configured [`Relay`].
110    ///
111    /// # Errors
112    ///
113    /// Returns [`Error::Config`] if the API key was not set.
114    pub fn build(self) -> Result<Relay, Error> {
115        let key = self
116            .api_key
117            .ok_or_else(|| Error::Config("relay API key is required".into()))?;
118        let http = HttpClient::new(key, self.base_url, self.timeout_secs)?;
119        Ok(Relay {
120            http: Arc::new(http),
121        })
122    }
123}
124
125/// Access to the `/v1/relay/messages` endpoints.
126///
127/// Obtain via [`Relay::messages`].
128pub struct MessagesClient {
129    http: Arc<HttpClient>,
130}
131
132impl MessagesClient {
133    /// Send an event to all subscribed endpoints.
134    ///
135    /// Maps to `POST /v1/relay/messages`.
136    ///
137    /// # Example
138    ///
139    /// ```no_run
140    /// use nautilus_rs::{Relay, SendMessageParams};
141    /// use serde_json::json;
142    ///
143    /// # async fn run() -> Result<(), nautilus_rs::Error> {
144    /// let relay = Relay::new("vrn_relay_live_sk_…");
145    /// let msg = relay.messages().send(SendMessageParams {
146    ///     event_type: "user.signed_up".into(),
147    ///     payload: json!({ "user_id": "usr_abc" }),
148    ///     ..Default::default()
149    /// }).await?;
150    /// println!("message id: {}", msg.id);
151    /// # Ok(())
152    /// # }
153    /// ```
154    pub async fn send(&self, params: SendMessageParams) -> Result<Message, Error> {
155        self.http.post("/v1/relay/messages", &params, false).await
156    }
157
158    /// Retrieve a paginated list of past messages.
159    ///
160    /// Maps to `GET /v1/relay/messages`.
161    ///
162    /// # Example
163    ///
164    /// ```no_run
165    /// use nautilus_rs::{Relay, ListMessagesParams};
166    ///
167    /// # async fn run() -> Result<(), nautilus_rs::Error> {
168    /// let relay = Relay::new("vrn_relay_live_sk_…");
169    /// let page = relay.messages().list(ListMessagesParams {
170    ///     limit: Some(20),
171    ///     event_type: Some("order.placed".into()),
172    ///     ..Default::default()
173    /// }).await?;
174    ///
175    /// for msg in &page.data {
176    ///     println!("{} — {}", msg.timestamp, msg.event_type);
177    /// }
178    /// # Ok(())
179    /// # }
180    /// ```
181    pub async fn list(&self, params: ListMessagesParams) -> Result<Paginated<Message>, Error> {
182        let mut query = vec![];
183        if let Some(limit) = params.limit {
184            query.push(format!("limit={limit}"));
185        }
186        if let Some(cursor) = &params.cursor {
187            query.push(format!("cursor={cursor}"));
188        }
189        if let Some(event_type) = &params.event_type {
190            query.push(format!("event_type={event_type}"));
191        }
192
193        let path = if query.is_empty() {
194            "/v1/relay/messages".to_string()
195        } else {
196            format!("/v1/relay/messages?{}", query.join("&"))
197        };
198
199        self.http.get(&path).await
200    }
201}