zai_rs/realtime/
client.rs1use std::sync::Arc;
4
5use super::session::SessionBuilder;
6use crate::{
7 ZaiResult,
8 client::{ApiFamily, endpoint::EndpointConfig, secret::ApiSecret},
9};
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub enum AuthMode {
14 Bearer,
16 Jwt {
21 ttl_seconds: i64,
23 },
24}
25
26#[derive(Clone)]
44pub struct RealtimeClient {
45 api_key: Arc<ApiSecret>,
46 auth: AuthMode,
47 endpoint_config: EndpointConfig,
48}
49
50impl RealtimeClient {
51 pub fn new(api_key: impl Into<String>) -> Self {
53 Self {
54 api_key: Arc::new(ApiSecret::new(api_key)),
55 auth: AuthMode::Bearer,
56 endpoint_config: EndpointConfig::defaults()
57 .expect("built-in realtime endpoint must be valid"),
58 }
59 }
60
61 pub fn with_jwt(mut self, ttl_seconds: i64) -> Self {
66 self.auth = AuthMode::Jwt { ttl_seconds };
67 self
68 }
69
70 pub fn with_bearer(mut self) -> Self {
72 self.auth = AuthMode::Bearer;
73 self
74 }
75
76 pub fn with_endpoint_config(mut self, config: EndpointConfig) -> Self {
78 self.endpoint_config = config;
79 self
80 }
81
82 pub fn try_with_realtime_base(mut self, base: impl AsRef<str>) -> ZaiResult<Self> {
87 self.endpoint_config =
88 self.endpoint_config
89 .with_base(ApiFamily::Realtime, base.as_ref(), false)?;
90 Ok(self)
91 }
92
93 pub fn session<M: super::RealtimeModel>(&self, model: M) -> SessionBuilder {
97 let realtime_url = self.realtime_url();
98 let model_name: String = model.into();
99 SessionBuilder::new(
100 Arc::clone(&self.api_key),
101 self.auth,
102 realtime_url,
103 model_name,
104 )
105 }
106
107 pub fn realtime_url(&self) -> String {
109 self.endpoint_config.base(ApiFamily::Realtime).to_string()
110 }
111
112 pub fn auth(&self) -> &AuthMode {
114 &self.auth
115 }
116}
117
118impl std::fmt::Debug for RealtimeClient {
119 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
120 formatter
121 .debug_struct("RealtimeClient")
122 .field("api_key", &self.api_key)
123 .field("auth", &self.auth)
124 .field("endpoint_config", &self.endpoint_config)
125 .finish()
126 }
127}
128
129#[cfg(test)]
130mod tests {
131 use super::*;
132
133 #[test]
134 fn debug_redacts_api_key() {
135 let client = RealtimeClient::new("abcdefghij.0123456789abcdef");
136 let debug = format!("{client:?}");
137 assert!(debug.contains("[REDACTED]"));
138 assert!(!debug.contains("abcdefghij"));
139 }
140
141 #[test]
142 fn realtime_override_accepts_an_owned_non_static_base() {
143 let base = format!("wss://{}/realtime", "example.com");
144 let client = RealtimeClient::new("abcdefghij.0123456789abcdef")
145 .try_with_realtime_base(&base)
146 .unwrap();
147 assert_eq!(client.realtime_url(), "wss://example.com/realtime");
148 assert!(
149 client
150 .try_with_realtime_base("ws://public.example/realtime")
151 .is_err()
152 );
153 }
154}