Skip to main content

radion_sdk/
client.rs

1//! The unified [`Radion`] client.
2
3use crate::config::{DEFAULT_BASE_URL, DEFAULT_WS_URL, RadionConfig};
4use crate::error::{RadionError, Result};
5
6/// Unified entry point for the Radion platform SDK.
7///
8/// Holds shared configuration and exposes each product surface as a field.
9/// Today that is [`realtime`](Radion::realtime); further surfaces attach here
10/// as they ship — the builder shape stays stable so adding them is additive.
11///
12/// Build one with [`Radion::builder`].
13#[derive(Debug)]
14#[non_exhaustive]
15pub struct Radion {
16    /// Shared configuration (API key, base/realtime URLs).
17    pub config: RadionConfig,
18
19    /// Realtime (WebSocket) product surface.
20    #[cfg(feature = "realtime")]
21    #[cfg_attr(docsrs, doc(cfg(feature = "realtime")))]
22    pub realtime: crate::realtime::RealtimeClient,
23}
24
25impl Radion {
26    /// Start building a client. Equivalent to [`RadionBuilder::default`].
27    pub fn builder() -> RadionBuilder {
28        RadionBuilder::default()
29    }
30}
31
32/// Builder for [`Radion`].
33///
34/// ```no_run
35/// # fn main() -> eyre::Result<()> {
36/// let radion = radion_sdk::Radion::builder()
37///     .api_key("sk_...")
38///     .build()?;
39/// # Ok(())
40/// # }
41/// ```
42#[derive(Debug, Default)]
43pub struct RadionBuilder {
44    api_key: Option<String>,
45    base_url: Option<String>,
46    ws_url: Option<String>,
47    #[cfg(feature = "realtime")]
48    token_provider: Option<crate::realtime::TokenProvider>,
49    #[cfg(feature = "realtime")]
50    auth_in_query: bool,
51    #[cfg(feature = "compression")]
52    compression: bool,
53}
54
55impl RadionBuilder {
56    /// Set the Radion API key (required). Sent as the `X-API-Key` header.
57    #[must_use]
58    pub fn api_key(mut self, api_key: impl Into<String>) -> Self {
59        self.api_key = Some(api_key.into());
60        self
61    }
62
63    /// Override the REST base URL. Defaults to [`DEFAULT_BASE_URL`].
64    #[must_use]
65    pub fn base_url(mut self, base_url: impl Into<String>) -> Self {
66        self.base_url = Some(base_url.into());
67        self
68    }
69
70    /// Override the realtime endpoint. Defaults to [`DEFAULT_WS_URL`].
71    #[must_use]
72    pub fn ws_url(mut self, ws_url: impl Into<String>) -> Self {
73        self.ws_url = Some(ws_url.into());
74        self
75    }
76
77    /// Set a static user JWT for the public-key (`pk_jwt_`) flow.
78    #[cfg(feature = "realtime")]
79    #[cfg_attr(docsrs, doc(cfg(feature = "realtime")))]
80    #[must_use]
81    pub fn token(mut self, token: impl Into<String>) -> Self {
82        self.token_provider = Some(crate::realtime::TokenProvider::from_static(token));
83        self
84    }
85
86    /// Set a user JWT provider, called on every (re)connect for a fresh token.
87    #[cfg(feature = "realtime")]
88    #[cfg_attr(docsrs, doc(cfg(feature = "realtime")))]
89    #[must_use]
90    pub fn token_provider(mut self, provider: crate::realtime::TokenProvider) -> Self {
91        self.token_provider = Some(provider);
92        self
93    }
94
95    /// Send credentials in the WS URL query string instead of headers.
96    #[cfg(feature = "realtime")]
97    #[cfg_attr(docsrs, doc(cfg(feature = "realtime")))]
98    #[must_use]
99    pub fn auth_in_query(mut self, enabled: bool) -> Self {
100        self.auth_in_query = enabled;
101        self
102    }
103
104    /// Ask the realtime server for zlib-compressed binary frames.
105    #[cfg(feature = "compression")]
106    #[cfg_attr(docsrs, doc(cfg(feature = "compression")))]
107    #[must_use]
108    pub fn compression(mut self, enabled: bool) -> Self {
109        self.compression = enabled;
110        self
111    }
112
113    /// Build the [`Radion`] client.
114    ///
115    /// # Errors
116    ///
117    /// Returns [`RadionError::Connection`] if the API key is missing or empty.
118    pub fn build(self) -> Result<Radion> {
119        let api_key = self.api_key.unwrap_or_default();
120        if api_key.is_empty() {
121            return Err(RadionError::connection("api_key is required"));
122        }
123        let config = RadionConfig {
124            api_key: api_key.clone(),
125            base_url: self
126                .base_url
127                .unwrap_or_else(|| DEFAULT_BASE_URL.to_string()),
128            ws_url: self.ws_url.unwrap_or_else(|| DEFAULT_WS_URL.to_string()),
129        };
130
131        #[cfg(feature = "realtime")]
132        let realtime = {
133            let mut options = crate::realtime::RealtimeOptions::new(api_key)
134                .url(config.ws_url.clone())
135                .auth_in_query(self.auth_in_query);
136            #[cfg(feature = "compression")]
137            {
138                options = options.compression(self.compression);
139            }
140            if let Some(provider) = self.token_provider {
141                options = options.token_provider(provider);
142            }
143            crate::realtime::RealtimeClient::new(options)
144        };
145
146        Ok(Radion {
147            config,
148            #[cfg(feature = "realtime")]
149            realtime,
150        })
151    }
152}
153
154#[cfg(all(test, feature = "realtime"))]
155mod builder_tests {
156    use super::*;
157
158    #[test]
159    fn builder_accepts_token_and_query_flag() {
160        let radion = Radion::builder()
161            .api_key("pk_jwt_x")
162            .token("jwt")
163            .auth_in_query(true)
164            .build()
165            .expect("builds");
166        let _ = radion;
167    }
168
169    #[cfg(feature = "compression")]
170    #[test]
171    fn builder_forwards_compression_to_the_realtime_options() {
172        let radion = Radion::builder()
173            .api_key("sk_x")
174            .compression(true)
175            .build()
176            .expect("builds");
177        let _ = radion;
178    }
179}