aerostream_client/
client.rs1use std::collections::HashMap;
2use std::sync::Arc;
3use std::time::Duration;
4
5use tokio::sync::RwLock;
6use tracing::debug;
7
8use crate::connection::AeroConnection;
9use crate::consumer::{AeroConsumer, ConsumerConfig};
10use crate::error::AeroError;
11use crate::producer::{AeroProducer, ProducerConfig};
12
13#[derive(Debug, Clone)]
15pub struct ClientConfig {
16 pub bootstrap_servers: Vec<String>,
18 pub token: Option<String>,
20 pub connect_timeout: Duration,
22}
23
24impl Default for ClientConfig {
25 fn default() -> Self {
26 Self {
27 bootstrap_servers: vec!["127.0.0.1:9091".to_string()],
28 token: None,
29 connect_timeout: Duration::from_secs(5),
30 }
31 }
32}
33
34impl ClientConfig {
35 pub fn builder() -> ClientConfigBuilder {
37 ClientConfigBuilder::default()
38 }
39}
40
41#[derive(Default)]
43pub struct ClientConfigBuilder {
44 config: ClientConfig,
45}
46
47impl ClientConfigBuilder {
48 pub fn bootstrap_servers(mut self, servers: Vec<String>) -> Self {
50 self.config.bootstrap_servers = servers;
51 self
52 }
53
54 pub fn bootstrap_server(mut self, server: impl Into<String>) -> Self {
56 self.config.bootstrap_servers = vec![server.into()];
57 self
58 }
59
60 pub fn token(mut self, token: impl Into<String>) -> Self {
62 self.config.token = Some(token.into());
63 self
64 }
65
66 pub fn connect_timeout(mut self, timeout: Duration) -> Self {
68 self.config.connect_timeout = timeout;
69 self
70 }
71
72 pub fn build(self) -> Result<ClientConfig, AeroError> {
74 if self.config.bootstrap_servers.is_empty() {
75 return Err(AeroError::InvalidConfiguration(
76 "bootstrap_servers cannot be empty".into(),
77 ));
78 }
79 Ok(self.config)
80 }
81}
82
83#[derive(Clone)]
85pub struct AeroClient {
86 config: Arc<ClientConfig>,
87 pool: Arc<RwLock<HashMap<String, AeroConnection>>>,
88 default_producer: Arc<RwLock<Option<AeroProducer>>>,
89}
90
91impl AeroClient {
92 pub fn new(config: ClientConfig) -> Self {
94 Self {
95 config: Arc::new(config),
96 pool: Arc::new(RwLock::new(HashMap::new())),
97 default_producer: Arc::new(RwLock::new(None)),
98 }
99 }
100
101 pub fn connect(addr: impl Into<String>) -> Result<Self, AeroError> {
103 let config = ClientConfig::builder().bootstrap_server(addr).build()?;
104 Ok(Self::new(config))
105 }
106
107 pub async fn get_connection(&self, broker_addr: &str) -> Result<AeroConnection, AeroError> {
109 {
110 let read = self.pool.read().await;
111 if let Some(conn) = read.get(broker_addr) {
112 if conn.is_alive() {
113 return Ok(conn.clone());
114 }
115 }
116 }
117
118 let mut write = self.pool.write().await;
119 if let Some(conn) = write.get(broker_addr) {
120 if conn.is_alive() {
121 return Ok(conn.clone());
122 }
123 }
124
125 debug!(addr = %broker_addr, "Establishing new pooled connection");
126 let conn = AeroConnection::connect(
127 broker_addr,
128 self.config.token.as_deref(),
129 self.config.connect_timeout,
130 )
131 .await?;
132
133 write.insert(broker_addr.to_string(), conn.clone());
134 Ok(conn)
135 }
136
137 pub fn producer(&self) -> AeroProducer {
139 let config = ProducerConfig::builder()
140 .bootstrap_servers(self.config.bootstrap_servers.clone())
141 .token(self.config.token.clone().unwrap_or_default())
142 .connect_timeout(self.config.connect_timeout)
143 .build()
144 .expect("valid producer configuration");
145 AeroProducer::new(config)
146 }
147
148 pub fn producer_with_config(&self, config: ProducerConfig) -> AeroProducer {
150 AeroProducer::new(config)
151 }
152
153 pub fn consumer(&self, topic: impl Into<String>, partition: u32) -> AeroConsumer {
155 let config = ConsumerConfig::builder()
156 .bootstrap_servers(self.config.bootstrap_servers.clone())
157 .token(self.config.token.clone().unwrap_or_default())
158 .topic(topic)
159 .partition(partition)
160 .connect_timeout(self.config.connect_timeout)
161 .build()
162 .expect("valid consumer configuration");
163 AeroConsumer::new(config)
164 }
165
166 pub fn consumer_with_config(&self, config: ConsumerConfig) -> AeroConsumer {
168 AeroConsumer::new(config)
169 }
170
171 pub async fn publish(
173 &self,
174 topic: &str,
175 partition: u32,
176 payload: &[u8],
177 ) -> Result<u64, AeroError> {
178 let producer = {
179 let read = self.default_producer.read().await;
180 if let Some(p) = &*read {
181 p.clone()
182 } else {
183 drop(read);
184 let mut write = self.default_producer.write().await;
185 if let Some(p) = &*write {
186 p.clone()
187 } else {
188 let p = self.producer();
189 *write = Some(p.clone());
190 p
191 }
192 }
193 };
194
195 producer.send(topic, partition, payload).await
196 }
197}