Skip to main content

wowlab_centrifuge/client/
config.rs

1use std::{pin::Pin, sync::Arc, time::Duration};
2
3use wowlab_types::{sensitive::Sensitive, sim::FastMap};
4
5use crate::error::Error;
6
7const DEFAULT_MIN_RECONNECT_DELAY: Duration = Duration::from_millis(500);
8const DEFAULT_MAX_RECONNECT_DELAY: Duration = Duration::from_secs(20);
9
10type BoxFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;
11
12type TokenCallback =
13    Arc<dyn Fn() -> BoxFuture<'static, Result<Sensitive<String>, Error>> + Send + Sync>;
14
15/// Connection identity, authentication, and reconnect settings for a [`crate::Client`].
16#[derive(Clone)]
17pub struct ClientConfig {
18    pub url: String,
19    pub token: Sensitive<String>,
20    pub name: String,
21    pub version: String,
22    pub data: Option<Vec<u8>>,
23    pub headers: FastMap<String, String>,
24    pub min_reconnect_delay: Duration,
25    pub max_reconnect_delay: Duration,
26    pub(super) get_token: Option<TokenCallback>,
27}
28
29impl ClientConfig {
30    /// Creates connection settings for `url` authenticated with `token`.
31    #[must_use]
32    pub fn new(url: impl Into<String>, token: Sensitive<String>) -> Self {
33        Self {
34            url: url.into(),
35            token,
36            name: "rust".to_string(),
37            version: env!("CARGO_PKG_VERSION").to_string(),
38            data: None,
39            headers: FastMap::default(),
40            min_reconnect_delay: DEFAULT_MIN_RECONNECT_DELAY,
41            max_reconnect_delay: DEFAULT_MAX_RECONNECT_DELAY,
42            get_token: None,
43        }
44    }
45
46    /// Sets the client implementation name sent during the handshake.
47    #[must_use]
48    pub fn name(mut self, name: impl Into<String>) -> Self {
49        self.name = name.into();
50
51        self
52    }
53
54    /// Sets the client implementation version sent during the handshake.
55    #[must_use]
56    pub fn version(mut self, version: impl Into<String>) -> Self {
57        self.version = version.into();
58
59        self
60    }
61
62    /// Sets application data sent during the handshake.
63    #[must_use]
64    pub fn data(mut self, data: Vec<u8>) -> Self {
65        self.data = Some(data);
66
67        self
68    }
69
70    /// Replaces all additional handshake headers.
71    #[must_use]
72    pub fn headers(mut self, headers: FastMap<String, String>) -> Self {
73        self.headers = headers;
74
75        self
76    }
77
78    /// Adds or replaces one additional handshake header.
79    #[must_use]
80    pub fn header(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
81        self.headers.insert(key.into(), value.into());
82
83        self
84    }
85
86    /// Installs the callback used to replace an expired connection token.
87    #[must_use]
88    pub fn get_token<F, Fut>(mut self, f: F) -> Self
89    where
90        F: Fn() -> Fut + Send + Sync + 'static,
91        Fut: Future<Output = Result<Sensitive<String>, Error>> + Send + 'static,
92    {
93        self.get_token = Some(Arc::new(move || Box::pin(f())));
94
95        self
96    }
97}
98
99impl std::fmt::Debug for ClientConfig {
100    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
101        f.debug_struct("ClientConfig")
102            .field("url", &self.url)
103            .field("token", &self.token)
104            .field("name", &self.name)
105            .field("version", &self.version)
106            .field("data", &self.data)
107            .field("headers", &self.headers)
108            .field("min_reconnect_delay", &self.min_reconnect_delay)
109            .field("max_reconnect_delay", &self.max_reconnect_delay)
110            .field("get_token", &self.get_token.as_ref().map(|_| "<callback>"))
111            .finish()
112    }
113}
114
115#[cfg(test)]
116mod tests {
117    use googletest::prelude::*;
118    use wowlab_types::sensitive::Sensitive;
119
120    use super::ClientConfig;
121
122    #[gtest]
123    fn debug_redacts_connection_token() -> Result<()> {
124        let config = ClientConfig::new(
125            "https://centrifugo.example.com",
126            Sensitive::new("connection-secret".to_string()),
127        );
128        let debug = format!("{config:?}");
129
130        verify_that!(debug.as_str(), contains_substring("[REDACTED]"))?;
131
132        verify_that!(debug.as_str(), not(contains_substring("connection-secret")))
133    }
134}