1use std::pin::Pin;
4use std::sync::Arc;
5use std::sync::Once;
6use tonic::transport::{Channel, ClientTlsConfig};
7
8fn ensure_crypto_provider() {
15 static INSTALL: Once = Once::new();
16 INSTALL.call_once(|| {
17 let _ = rustls::crypto::ring::default_provider().install_default();
19 });
20}
21
22use force::auth::Authenticator;
23use force::session::Session;
24use serde::Serialize;
25use serde::de::DeserializeOwned;
26use serde_json::Value;
27use tokio::sync::OnceCell;
28use tokio_stream::Stream;
29
30use crate::config::{PubSubConfig, ReplayPreset};
31use crate::error::{PubSubError, Result};
32use crate::interceptor;
33use crate::publish_sink::{PublishSink, open_publish_stream};
34use crate::publisher::publish_unary;
35use crate::schema_cache::SchemaCache;
36use crate::subscriber::{subscribe_dynamic, subscribe_typed_dynamic};
37use crate::types::{PubSubEvent, PublishResponse};
38
39use crate::proto::eventbus_v1::{SchemaRequest, TopicRequest, pub_sub_client::PubSubClient};
40
41#[derive(serde::Deserialize)]
43struct UserInfo {
44 organization_id: String,
45}
46
47async fn fetch_tenant_id<A: Authenticator>(session: &Arc<Session<A>>) -> Result<String> {
51 let token = session.token_manager().token().await?;
52 let userinfo_url = format!("{}/services/oauth2/userinfo", token.instance_url());
53
54 let resp = reqwest::Client::new()
55 .get(&userinfo_url)
56 .bearer_auth(token.as_str())
57 .send()
58 .await
59 .map_err(|e| PubSubError::Config(format!("userinfo request failed: {e}")))?;
60
61 if !resp.status().is_success() {
62 return Err(PubSubError::Config(format!(
63 "userinfo returned status {}",
64 resp.status()
65 )));
66 }
67
68 let body = force::http::read_capped_body(resp, 1024 * 1024)
69 .await
70 .map_err(|e| PubSubError::Config(format!("userinfo parse failed: {e}")))?;
71
72 let info: UserInfo = serde_json::from_str(&body)
73 .map_err(|e| PubSubError::Config(format!("userinfo parse failed: {e}")))?;
74
75 Ok(info.organization_id)
76}
77
78#[derive(Debug, Clone)]
80pub struct TopicInfo {
81 pub topic_name: String,
83 pub topic_uri: String,
85 pub can_publish: bool,
87 pub can_subscribe: bool,
89 pub schema_id: String,
91}
92
93#[derive(Debug, Clone)]
95pub struct SchemaInfo {
96 pub schema_id: String,
98 pub schema_json: String,
100}
101
102#[derive(Clone)]
107pub struct PubSubHandler<A: Authenticator> {
108 pub(crate) session: Arc<Session<A>>,
109 pub(crate) config: PubSubConfig,
111 pub schema_cache: SchemaCache,
113 pub(crate) channel: Channel,
114 tenant_id: Arc<OnceCell<String>>,
118}
119
120impl<A: Authenticator> PubSubHandler<A> {
121 pub async fn connect(session: Arc<Session<A>>, config: PubSubConfig) -> Result<Self> {
131 if config.batch_size < 1 || config.batch_size > 100 {
132 return Err(PubSubError::Config(
133 "batch_size must be between 1 and 100".to_string(),
134 ));
135 }
136
137 let endpoint = Channel::from_shared(config.endpoint.clone())
138 .map_err(|e| PubSubError::Config(format!("invalid endpoint: {e}")))?;
139 let endpoint = if endpoint.uri().scheme_str() == Some("https") {
140 ensure_crypto_provider();
141 endpoint.tls_config(ClientTlsConfig::new().with_webpki_roots())?
142 } else {
143 endpoint
144 };
145 let channel = endpoint.connect().await?;
146
147 Ok(Self {
148 session,
149 config,
150 schema_cache: SchemaCache::new(),
151 channel,
152 tenant_id: Arc::new(OnceCell::new()),
153 })
154 }
155
156 fn grpc_client(&self) -> PubSubClient<Channel> {
158 PubSubClient::new(self.channel.clone())
159 }
160
161 pub(crate) async fn get_tenant_id(&self) -> Result<&str> {
171 self.tenant_id
172 .get_or_try_init(|| fetch_tenant_id(&self.session))
173 .await
174 .map(String::as_str)
175 }
176
177 async fn auth_request<T>(&self, message: T) -> Result<tonic::Request<T>> {
182 let token = self.session.token_manager().token().await?;
183 let tenant_id = self.get_tenant_id().await?.to_string();
184 let meta = interceptor::build_metadata(&token, token.instance_url(), &tenant_id)?;
185 let mut req = tonic::Request::new(message);
186 *req.metadata_mut() = meta;
187 Ok(req)
188 }
189
190 pub async fn get_topic(&self, topic_name: &str) -> Result<TopicInfo> {
196 let req = self
197 .auth_request(TopicRequest {
198 topic_name: topic_name.to_string(),
199 })
200 .await?;
201
202 let resp = self.grpc_client().get_topic(req).await?;
203 let info = resp.into_inner();
204 Ok(TopicInfo {
205 topic_name: info.topic_name,
206 topic_uri: info.topic_uri,
207 can_publish: info.can_publish,
208 can_subscribe: info.can_subscribe,
209 schema_id: info.schema_id,
210 })
211 }
212
213 pub async fn get_schema(&self, schema_id: &str) -> Result<SchemaInfo> {
222 let req = self
223 .auth_request(SchemaRequest {
224 schema_id: schema_id.to_string(),
225 })
226 .await?;
227
228 let resp = self.grpc_client().get_schema(req).await?;
229 let info = resp.into_inner();
230 Ok(SchemaInfo {
231 schema_id: info.schema_id,
232 schema_json: info.schema_json,
233 })
234 }
235}
236
237impl<A: Authenticator + Send + Sync + 'static> PubSubHandler<A> {
238 pub async fn publish<T: Serialize + Send>(
249 &self,
250 topic: &str,
251 events: Vec<T>,
252 ) -> Result<PublishResponse> {
253 let topic_info = self.get_topic(topic).await?;
254 let schema_id = &topic_info.schema_id;
255
256 let token = self.session.token_manager().token().await?;
258 let tenant_id = self.get_tenant_id().await?.to_string();
259 let meta = interceptor::build_metadata(&token, token.instance_url(), &tenant_id)?;
260 self.schema_cache
261 .get_or_fetch(schema_id, &self.channel, meta)
262 .await?;
263
264 publish_unary(
265 &self.session,
266 &self.channel,
267 &self.schema_cache,
268 schema_id,
269 topic,
270 events,
271 &tenant_id,
272 )
273 .await
274 }
275
276 pub async fn subscribe(
285 &self,
286 topic: &str,
287 replay: ReplayPreset,
288 ) -> Result<Pin<Box<dyn Stream<Item = Result<PubSubEvent<Value>>> + Send>>> {
289 let tenant_id = self.get_tenant_id().await?.to_string();
290 Ok(subscribe_dynamic(
291 Arc::clone(&self.session),
292 self.config.clone(),
293 self.schema_cache.clone(),
294 self.channel.clone(),
295 topic.to_string(),
296 replay,
297 tenant_id,
298 ))
299 }
300
301 pub async fn subscribe_typed<T>(
307 &self,
308 topic: &str,
309 replay: ReplayPreset,
310 ) -> Result<Pin<Box<dyn Stream<Item = Result<PubSubEvent<T>>> + Send>>>
311 where
312 T: DeserializeOwned + Send + 'static,
313 {
314 let tenant_id = self.get_tenant_id().await?.to_string();
315 Ok(subscribe_typed_dynamic(
316 Arc::clone(&self.session),
317 self.config.clone(),
318 self.schema_cache.clone(),
319 self.channel.clone(),
320 topic.to_string(),
321 replay,
322 tenant_id,
323 ))
324 }
325
326 pub async fn publish_stream<T: Serialize + Send + 'static>(
338 &self,
339 topic: &str,
340 ) -> Result<PublishSink<T>> {
341 let token = self.session.token_manager().token().await?;
342 let tenant_id = self.get_tenant_id().await?.to_string();
343
344 open_publish_stream(
345 Arc::clone(&self.session),
346 self.channel.clone(),
347 self.schema_cache.clone(),
348 tenant_id,
349 topic.to_string(),
350 &token,
351 )
352 .await
353 }
354}