1use std::sync::Arc;
49
50use tokio::sync::RwLock;
51
52use rust_tg_bot_raw::bot::Bot;
53use rust_tg_bot_raw::request::base::BaseRequest;
54
55use crate::callback_data_cache::CallbackDataCache;
56use crate::defaults::Defaults;
57
58#[cfg(feature = "rate-limiter")]
59use crate::rate_limiter::{DynRateLimiter, RateLimitedRequest};
60
61pub struct ExtBot {
81 bot: Bot,
83
84 defaults: Option<Defaults>,
86
87 callback_data_cache: Option<Arc<RwLock<CallbackDataCache>>>,
89
90 #[cfg(feature = "rate-limiter")]
93 rate_limiter: Option<Arc<dyn DynRateLimiter>>,
94
95 #[cfg(not(feature = "rate-limiter"))]
97 rate_limiter: Option<()>,
98}
99
100impl std::ops::Deref for ExtBot {
105 type Target = Bot;
106
107 fn deref(&self) -> &Bot {
108 &self.bot
109 }
110}
111
112impl std::fmt::Debug for ExtBot {
113 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
114 f.debug_struct("ExtBot")
115 .field("token", &self.bot.token())
116 .field("defaults", &self.defaults)
117 .field(
118 "has_callback_data_cache",
119 &self.callback_data_cache.is_some(),
120 )
121 .field("has_rate_limiter", &self.rate_limiter.is_some())
122 .finish()
123 }
124}
125
126impl ExtBot {
127 #[cfg(feature = "rate-limiter")]
140 #[must_use]
141 pub(crate) fn new(
142 bot: Bot,
143 defaults: Option<Defaults>,
144 arbitrary_callback_data: Option<usize>,
145 rate_limiter: Option<Arc<dyn DynRateLimiter>>,
146 ) -> Self {
147 let callback_data_cache = arbitrary_callback_data.map(|maxsize| {
148 let effective = if maxsize == 0 { 1024 } else { maxsize };
149 Arc::new(RwLock::new(CallbackDataCache::new(effective)))
150 });
151
152 Self {
153 bot,
154 defaults,
155 callback_data_cache,
156 rate_limiter,
157 }
158 }
159
160 #[cfg(not(feature = "rate-limiter"))]
162 #[must_use]
163 pub(crate) fn new(
164 bot: Bot,
165 defaults: Option<Defaults>,
166 arbitrary_callback_data: Option<usize>,
167 rate_limiter: Option<()>,
168 ) -> Self {
169 let callback_data_cache = arbitrary_callback_data.map(|maxsize| {
170 let effective = if maxsize == 0 { 1024 } else { maxsize };
171 Arc::new(RwLock::new(CallbackDataCache::new(effective)))
172 });
173
174 Self {
175 bot,
176 defaults,
177 callback_data_cache,
178 rate_limiter,
179 }
180 }
181
182 #[must_use]
194 pub fn from_bot(bot: Bot) -> Self {
195 Self::new(bot, None, None, None)
196 }
197
198 #[must_use]
204 pub fn inner(&self) -> &Bot {
205 &self.bot
206 }
207
208 #[must_use]
210 pub fn token(&self) -> &str {
211 self.bot.token()
212 }
213
214 #[must_use]
216 pub fn defaults(&self) -> Option<&Defaults> {
217 self.defaults.as_ref()
218 }
219
220 #[must_use]
222 pub fn callback_data_cache(&self) -> Option<&Arc<RwLock<CallbackDataCache>>> {
223 self.callback_data_cache.as_ref()
224 }
225
226 #[must_use]
228 pub fn has_callback_data_cache(&self) -> bool {
229 self.callback_data_cache.is_some()
230 }
231
232 #[must_use]
234 pub fn has_rate_limiter(&self) -> bool {
235 self.rate_limiter.is_some()
236 }
237
238 #[cfg(feature = "rate-limiter")]
240 #[must_use]
241 pub fn rate_limiter(&self) -> Option<&Arc<dyn DynRateLimiter>> {
242 self.rate_limiter.as_ref()
243 }
244
245 #[cfg(not(feature = "rate-limiter"))]
247 #[must_use]
248 pub fn rate_limiter(&self) -> Option<()> {
249 self.rate_limiter
250 }
251
252 #[must_use]
254 pub fn builder(token: impl Into<String>, request: Arc<dyn BaseRequest>) -> ExtBotBuilder {
255 ExtBotBuilder::new(token, request)
256 }
257
258 pub async fn initialize(&self) -> rust_tg_bot_raw::error::Result<()> {
262 #[cfg(feature = "rate-limiter")]
263 if let Some(ref rl) = self.rate_limiter {
264 rl.initialize().await;
265 }
266 Ok(())
267 }
268
269 pub async fn shutdown(&self) -> rust_tg_bot_raw::error::Result<()> {
273 #[cfg(feature = "rate-limiter")]
274 if let Some(ref rl) = self.rate_limiter {
275 rl.shutdown().await;
276 }
277 Ok(())
278 }
279}
280
281pub struct ExtBotBuilder {
297 token: String,
298 request: Arc<dyn BaseRequest>,
299 base_url: Option<String>,
300 base_file_url: Option<String>,
301 defaults: Option<Defaults>,
302 arbitrary_callback_data: Option<usize>,
303 #[cfg(feature = "rate-limiter")]
304 rate_limiter: Option<Arc<dyn DynRateLimiter>>,
305 #[cfg(not(feature = "rate-limiter"))]
306 rate_limiter: Option<()>,
307}
308
309impl ExtBotBuilder {
310 #[must_use]
312 pub fn new(token: impl Into<String>, request: Arc<dyn BaseRequest>) -> Self {
313 Self {
314 token: token.into(),
315 request,
316 base_url: None,
317 base_file_url: None,
318 defaults: None,
319 arbitrary_callback_data: None,
320 rate_limiter: None,
321 }
322 }
323
324 #[must_use]
326 pub fn base_url(mut self, url: impl Into<String>) -> Self {
327 self.base_url = Some(url.into());
328 self
329 }
330
331 #[must_use]
333 pub fn base_file_url(mut self, url: impl Into<String>) -> Self {
334 self.base_file_url = Some(url.into());
335 self
336 }
337
338 #[must_use]
340 pub fn defaults(mut self, defaults: Defaults) -> Self {
341 self.defaults = Some(defaults);
342 self
343 }
344
345 #[must_use]
349 pub fn arbitrary_callback_data(mut self, maxsize: usize) -> Self {
350 self.arbitrary_callback_data = Some(maxsize);
351 self
352 }
353
354 #[cfg(feature = "rate-limiter")]
360 #[must_use]
361 pub fn rate_limiter(mut self, rl: Arc<dyn DynRateLimiter>) -> Self {
362 self.rate_limiter = Some(rl);
363 self
364 }
365
366 #[cfg(not(feature = "rate-limiter"))]
368 #[must_use]
369 pub fn rate_limiter(mut self, _rl: ()) -> Self {
370 self.rate_limiter = Some(());
371 self
372 }
373
374 #[must_use]
379 pub fn build(self) -> ExtBot {
380 #[cfg(feature = "rate-limiter")]
381 let (request, rate_limiter) = if let Some(ref rl) = self.rate_limiter {
382 let wrapped: Arc<dyn BaseRequest> =
383 Arc::new(RateLimitedRequest::new(self.request.clone(), rl.clone()));
384 (wrapped, self.rate_limiter)
385 } else {
386 (self.request, None)
387 };
388
389 #[cfg(not(feature = "rate-limiter"))]
390 let (request, rate_limiter) = (self.request, self.rate_limiter);
391
392 let bot = Bot::new(&self.token, request);
393
394 ExtBot::new(
395 bot,
396 self.defaults,
397 self.arbitrary_callback_data,
398 rate_limiter,
399 )
400 }
401}
402
403impl std::fmt::Debug for ExtBotBuilder {
404 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
405 f.debug_struct("ExtBotBuilder")
406 .field("token", &"[REDACTED]")
407 .field("has_rate_limiter", &self.rate_limiter.is_some())
408 .finish()
409 }
410}
411
412#[cfg(test)]
420pub(crate) mod test_support {
421 use std::time::Duration;
422
423 use rust_tg_bot_raw::request::base::{HttpMethod, TimeoutOverride};
424 use rust_tg_bot_raw::request::request_data::RequestData;
425
426 use super::*;
427
428 #[derive(Debug)]
429 pub struct MockRequest;
430
431 #[async_trait::async_trait]
432 impl BaseRequest for MockRequest {
433 async fn initialize(&self) -> rust_tg_bot_raw::error::Result<()> {
434 Ok(())
435 }
436
437 async fn shutdown(&self) -> rust_tg_bot_raw::error::Result<()> {
438 Ok(())
439 }
440
441 fn default_read_timeout(&self) -> Option<Duration> {
442 Some(Duration::from_secs(5))
443 }
444
445 async fn do_request(
446 &self,
447 _url: &str,
448 _method: HttpMethod,
449 _request_data: Option<&RequestData>,
450 _timeouts: TimeoutOverride,
451 ) -> rust_tg_bot_raw::error::Result<(u16, bytes::Bytes)> {
452 let body = br#"{"ok":true,"result":[]}"#;
453 Ok((200, bytes::Bytes::from_static(body)))
454 }
455
456 async fn do_request_json_bytes(
457 &self,
458 _url: &str,
459 _body: &[u8],
460 _timeouts: TimeoutOverride,
461 ) -> rust_tg_bot_raw::error::Result<(u16, bytes::Bytes)> {
462 let body = br#"{"ok":true,"result":[]}"#;
463 Ok((200, bytes::Bytes::from_static(body)))
464 }
465 }
466
467 pub fn mock_request() -> Arc<dyn BaseRequest> {
468 Arc::new(MockRequest)
469 }
470}
471
472#[cfg(test)]
473mod tests {
474 use super::*;
475 use test_support::*;
476
477 #[test]
478 fn ext_bot_creation() {
479 let bot = Bot::new("test_token", mock_request());
480 let ext = ExtBot::from_bot(bot);
481
482 assert_eq!(ext.token(), "test_token");
483 assert!(ext.defaults().is_none());
484 assert!(!ext.has_callback_data_cache());
485 assert!(!ext.has_rate_limiter());
486 }
487
488 #[test]
489 fn ext_bot_with_callback_cache() {
490 let bot = Bot::new("token", mock_request());
491 let ext = ExtBot::new(bot, None, Some(512), None);
492
493 assert!(ext.has_callback_data_cache());
494 }
495
496 #[test]
497 fn ext_bot_with_defaults() {
498 let defaults = Defaults::builder().parse_mode("HTML").build();
499 let bot = Bot::new("token", mock_request());
500 let ext = ExtBot::new(bot, Some(defaults), None, None);
501
502 assert_eq!(ext.defaults().unwrap().parse_mode(), Some("HTML"));
503 }
504
505 #[test]
506 fn ext_bot_builder() {
507 let ext = ExtBot::builder("my_token", mock_request())
508 .arbitrary_callback_data(256)
509 .build();
510
511 assert_eq!(ext.token(), "my_token");
512 assert!(ext.has_callback_data_cache());
513 }
514
515 #[tokio::test]
516 async fn ext_bot_lifecycle() {
517 let bot = Bot::new("token", mock_request());
518 let ext = ExtBot::from_bot(bot);
519 assert!(ext.initialize().await.is_ok());
520 assert!(ext.shutdown().await.is_ok());
521 }
522
523 #[test]
524 fn ext_bot_debug() {
525 let bot = Bot::new("token", mock_request());
526 let ext = ExtBot::from_bot(bot);
527 let s = format!("{ext:?}");
528 assert!(s.contains("ExtBot"));
529 assert!(s.contains("token"));
530 }
531
532 #[test]
533 fn ext_bot_from_bot_convenience() {
534 let bot = Bot::new("tk", mock_request());
535 let ext = ExtBot::from_bot(bot);
536 assert_eq!(ext.token(), "tk");
537 assert!(ext.defaults().is_none());
538 assert!(!ext.has_callback_data_cache());
539 assert!(!ext.has_rate_limiter());
540 }
541
542 #[test]
543 fn ext_bot_deref_provides_bot_methods() {
544 let bot = Bot::new("deref_token", mock_request());
545 let ext = ExtBot::from_bot(bot);
546
547 let deref_token: &str = (*ext).token();
549 assert_eq!(deref_token, "deref_token");
550 assert_eq!(ext.token(), deref_token);
551 }
552
553 #[cfg(feature = "rate-limiter")]
554 #[test]
555 fn ext_bot_builder_with_rate_limiter() {
556 use crate::rate_limiter::NoRateLimiter;
557
558 let limiter: Arc<dyn DynRateLimiter> = Arc::new(NoRateLimiter);
559 let ext = ExtBot::builder("rl_token", mock_request())
560 .rate_limiter(limiter)
561 .build();
562
563 assert_eq!(ext.token(), "rl_token");
564 assert!(ext.has_rate_limiter());
565 assert!(ext.rate_limiter().is_some());
566 }
567
568 #[cfg(feature = "rate-limiter")]
569 #[test]
570 fn ext_bot_builder_without_rate_limiter() {
571 let ext = ExtBot::builder("no_rl", mock_request()).build();
572
573 assert!(!ext.has_rate_limiter());
574 assert!(ext.rate_limiter().is_none());
575 }
576
577 #[cfg(feature = "rate-limiter")]
578 #[tokio::test]
579 async fn ext_bot_lifecycle_with_rate_limiter() {
580 use crate::rate_limiter::NoRateLimiter;
581
582 let limiter: Arc<dyn DynRateLimiter> = Arc::new(NoRateLimiter);
583 let ext = ExtBot::builder("rl_lc", mock_request())
584 .rate_limiter(limiter)
585 .build();
586
587 assert!(ext.initialize().await.is_ok());
588 assert!(ext.shutdown().await.is_ok());
589 }
590}