use std::sync::Arc;
use crate::config::Auth;
#[derive(Clone)]
pub struct Client {
pub(crate) inner: Arc<Inner>,
}
pub(crate) struct Inner {
pub(crate) http: reqwest::Client,
pub(crate) base: String,
pub(crate) auth_header: String,
pub(crate) max_retries: u32,
}
impl std::fmt::Debug for Client {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Client")
.field("base", &self.inner.base)
.field("auth", &"<redacted>")
.finish()
}
}
impl Client {
pub fn builder(auth: Auth) -> ClientBuilder {
ClientBuilder::new(auth)
}
pub fn room(&self, name: impl Into<crate::types::RoomName>) -> crate::room::Room {
crate::room::Room::new(self.clone(), name.into())
}
}
pub struct ClientBuilder {
auth: Auth,
host: String,
http: Option<reqwest::Client>,
timeout: Option<std::time::Duration>,
max_retries: u32,
}
impl ClientBuilder {
pub fn new(auth: Auth) -> Self {
Self {
auth,
host: "https://rest.ably.io".into(),
http: None,
timeout: None,
max_retries: 3,
}
}
pub fn host(mut self, host: impl Into<String>) -> Self {
self.host = host.into();
self
}
pub fn http_client(mut self, client: reqwest::Client) -> Self {
self.http = Some(client);
self
}
pub fn timeout(mut self, timeout: std::time::Duration) -> Self {
self.timeout = Some(timeout);
self
}
pub fn max_retries(mut self, n: u32) -> Self {
self.max_retries = n;
self
}
pub fn build(self) -> Client {
let http = self.http.unwrap_or_else(|| {
let mut b = reqwest::Client::builder();
if let Some(t) = self.timeout {
b = b.timeout(t);
}
b.build().expect("failed to build reqwest client")
});
Client {
inner: Arc::new(Inner {
http,
base: self.host.trim_end_matches('/').to_string(),
auth_header: self.auth.header_value(),
max_retries: self.max_retries,
}),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn client_debug_redacts_credentials() {
let client = Client::builder(Auth::api_key("app.key:supersecret"))
.host("https://example.test")
.build();
let dbg = format!("{client:?}");
assert!(!dbg.contains("supersecret"));
assert!(!dbg.contains("YXBw")); assert!(dbg.contains("https://example.test"));
}
#[test]
fn host_trailing_slash_is_trimmed() {
let client = Client::builder(Auth::token("t"))
.host("https://example.test/")
.build();
assert_eq!(client.inner.base, "https://example.test");
}
}