1use std::sync::atomic::{AtomicU64, Ordering};
10use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
11
12use uuid::Uuid;
13
14static SEQUENTIAL_COUNTER: AtomicU64 = AtomicU64::new(0);
20
21#[inline]
23pub fn generate_uuid() -> String {
24 Uuid::new_v4().to_string()
25}
26
27#[inline]
29pub fn generate_short_uuid() -> String {
30 Uuid::new_v4().to_string()[..8].to_string()
31}
32
33#[inline]
35pub fn generate_sequential_id(prefix: &str) -> String {
36 let seq = SEQUENTIAL_COUNTER.fetch_add(1, Ordering::SeqCst);
37 format!("{}-{:08}", prefix, seq)
38}
39
40#[inline]
42pub fn generate_timestamp_id(prefix: &str) -> String {
43 let ts = SystemTime::now()
44 .duration_since(UNIX_EPOCH)
45 .unwrap_or_default()
46 .as_micros();
47 format!("{}-{}", prefix, ts)
48}
49
50#[inline]
56pub fn current_timestamp_ms() -> u64 {
57 SystemTime::now()
58 .duration_since(UNIX_EPOCH)
59 .unwrap_or_default()
60 .as_millis() as u64
61}
62
63#[inline]
65pub fn current_timestamp_us() -> u64 {
66 SystemTime::now()
67 .duration_since(UNIX_EPOCH)
68 .unwrap_or_default()
69 .as_micros() as u64
70}
71
72#[inline]
74pub fn current_timestamp_ns() -> u128 {
75 SystemTime::now()
76 .duration_since(UNIX_EPOCH)
77 .unwrap_or_default()
78 .as_nanos()
79}
80
81pub fn format_duration(duration: Duration) -> String {
83 let secs = duration.as_secs();
84 let millis = duration.subsec_millis();
85
86 if secs >= 3600 {
87 let hours = secs / 3600;
88 let mins = (secs % 3600) / 60;
89 format!("{}h {}m", hours, mins)
90 } else if secs >= 60 {
91 let mins = secs / 60;
92 let secs = secs % 60;
93 format!("{}m {}s", mins, secs)
94 } else if secs > 0 {
95 format!("{}.{:03}s", secs, millis)
96 } else if millis > 0 {
97 format!("{}ms", millis)
98 } else {
99 format!("{}µs", duration.as_micros())
100 }
101}
102
103#[derive(Debug, Clone)]
105pub struct Stopwatch {
106 start: Instant,
107 laps: Vec<(String, Duration)>,
108}
109
110impl Stopwatch {
111 pub fn start() -> Self {
113 Self {
114 start: Instant::now(),
115 laps: Vec::new(),
116 }
117 }
118
119 pub fn elapsed(&self) -> Duration {
121 self.start.elapsed()
122 }
123
124 pub fn lap(&mut self, name: impl Into<String>) {
126 self.laps.push((name.into(), self.elapsed()));
127 }
128
129 pub fn laps(&self) -> &[(String, Duration)] {
131 &self.laps
132 }
133
134 pub fn reset(&mut self) {
136 self.start = Instant::now();
137 self.laps.clear();
138 }
139
140 pub fn elapsed_ms(&self) -> u64 {
142 self.elapsed().as_millis() as u64
143 }
144
145 pub fn elapsed_us(&self) -> u64 {
147 self.elapsed().as_micros() as u64
148 }
149}
150
151impl Default for Stopwatch {
152 fn default() -> Self {
153 Self::start()
154 }
155}
156
157pub fn truncate_string(s: &str, max_len: usize) -> String {
163 if s.len() <= max_len {
164 s.to_string()
165 } else {
166 format!("{}...", &s[..max_len.saturating_sub(3)])
167 }
168}
169
170pub fn format_bytes(bytes: u64) -> String {
172 const KB: u64 = 1024;
173 const MB: u64 = KB * 1024;
174 const GB: u64 = MB * 1024;
175 const TB: u64 = GB * 1024;
176
177 if bytes >= TB {
178 format!("{:.2} TB", bytes as f64 / TB as f64)
179 } else if bytes >= GB {
180 format!("{:.2} GB", bytes as f64 / GB as f64)
181 } else if bytes >= MB {
182 format!("{:.2} MB", bytes as f64 / MB as f64)
183 } else if bytes >= KB {
184 format!("{:.2} KB", bytes as f64 / KB as f64)
185 } else {
186 format!("{} B", bytes)
187 }
188}
189
190pub fn sanitize_identifier(s: &str) -> String {
192 s.chars()
193 .map(|c| {
194 if c.is_alphanumeric() || c == '_' || c == '-' {
195 c
196 } else {
197 '_'
198 }
199 })
200 .collect()
201}
202
203#[macro_export]
223macro_rules! builder_setter {
224 ($field:ident, $type:ty) => {
225 pub fn $field(mut self, value: $type) -> Self {
226 self.$field = value;
227 self
228 }
229 };
230 ($field:ident, $type:ty, $doc:expr) => {
231 #[doc = $doc]
232 pub fn $field(mut self, value: $type) -> Self {
233 self.$field = value;
234 self
235 }
236 };
237}
238
239#[macro_export]
251macro_rules! builder_setter_into {
252 ($field:ident, $type:ty) => {
253 pub fn $field(mut self, value: impl Into<$type>) -> Self {
254 self.$field = value.into();
255 self
256 }
257 };
258 ($field:ident, $type:ty, $doc:expr) => {
259 #[doc = $doc]
260 pub fn $field(mut self, value: impl Into<$type>) -> Self {
261 self.$field = value.into();
262 self
263 }
264 };
265}
266
267#[macro_export]
277macro_rules! builder_setter_option {
278 ($field:ident, $type:ty) => {
279 pub fn $field(mut self, value: impl Into<$type>) -> Self {
280 self.$field = Some(value.into());
281 self
282 }
283 };
284 ($field:ident, $type:ty, $doc:expr) => {
285 #[doc = $doc]
286 pub fn $field(mut self, value: impl Into<$type>) -> Self {
287 self.$field = Some(value.into());
288 self
289 }
290 };
291}
292
293#[macro_export]
303macro_rules! builder_with {
304 ($field:ident, $type:ty) => {
305 paste::paste! {
306 pub fn [<with_ $field>](mut self, value: impl Into<$type>) -> Self {
307 self.$field = value.into();
308 self
309 }
310 }
311 };
312 ($field:ident, $type:ty, $doc:expr) => {
313 paste::paste! {
314 #[doc = $doc]
315 pub fn [<with_ $field>](mut self, value: impl Into<$type>) -> Self {
316 self.$field = value.into();
317 self
318 }
319 }
320 };
321}
322
323#[derive(Debug, Clone)]
329pub struct RetryConfig {
330 pub max_attempts: u32,
332 pub initial_delay: Duration,
334 pub max_delay: Duration,
336 pub multiplier: f64,
338}
339
340impl Default for RetryConfig {
341 fn default() -> Self {
342 Self {
343 max_attempts: 3,
344 initial_delay: Duration::from_millis(100),
345 max_delay: Duration::from_secs(10),
346 multiplier: 2.0,
347 }
348 }
349}
350
351impl RetryConfig {
352 pub fn new(max_attempts: u32) -> Self {
354 Self {
355 max_attempts,
356 ..Default::default()
357 }
358 }
359
360 pub fn with_initial_delay(mut self, delay: Duration) -> Self {
362 self.initial_delay = delay;
363 self
364 }
365
366 pub fn with_max_delay(mut self, delay: Duration) -> Self {
368 self.max_delay = delay;
369 self
370 }
371
372 pub fn with_multiplier(mut self, multiplier: f64) -> Self {
374 self.multiplier = multiplier;
375 self
376 }
377
378 pub fn delay_for_attempt(&self, attempt: u32) -> Duration {
380 if attempt == 0 {
381 return Duration::ZERO;
382 }
383
384 let delay_ms = self.initial_delay.as_millis() as f64
385 * self.multiplier.powi(attempt.saturating_sub(1) as i32);
386 let delay = Duration::from_millis(delay_ms.min(self.max_delay.as_millis() as f64) as u64);
387
388 delay.min(self.max_delay)
389 }
390}
391
392pub async fn retry_async<F, Fut, T, E>(config: RetryConfig, mut f: F) -> Result<T, E>
408where
409 F: FnMut() -> Fut,
410 Fut: std::future::Future<Output = Result<T, E>>,
411{
412 let mut last_error = None;
413
414 for attempt in 0..config.max_attempts {
415 match f().await {
416 Ok(value) => return Ok(value),
417 Err(e) => {
418 last_error = Some(e);
419
420 if attempt + 1 < config.max_attempts {
421 let delay = config.delay_for_attempt(attempt + 1);
422 tokio::time::sleep(delay).await;
423 }
424 }
425 }
426 }
427
428 Err(last_error.expect("Should have at least one error"))
429}
430
431#[derive(Debug)]
437pub struct RateLimiter {
438 capacity: u64,
440 tokens: AtomicU64,
442 refill_rate: f64,
444 last_refill: parking_lot::Mutex<Instant>,
446}
447
448impl RateLimiter {
449 pub fn new(capacity: u64, refill_rate: f64) -> Self {
451 Self {
452 capacity,
453 tokens: AtomicU64::new(capacity),
454 refill_rate,
455 last_refill: parking_lot::Mutex::new(Instant::now()),
456 }
457 }
458
459 pub fn try_acquire(&self) -> bool {
461 self.refill();
462
463 loop {
464 let current = self.tokens.load(Ordering::Acquire);
465 if current == 0 {
466 return false;
467 }
468
469 if self
470 .tokens
471 .compare_exchange_weak(current, current - 1, Ordering::AcqRel, Ordering::Acquire)
472 .is_ok()
473 {
474 return true;
475 }
476 }
477 }
478
479 pub fn try_acquire_n(&self, n: u64) -> bool {
481 self.refill();
482
483 loop {
484 let current = self.tokens.load(Ordering::Acquire);
485 if current < n {
486 return false;
487 }
488
489 if self
490 .tokens
491 .compare_exchange_weak(current, current - n, Ordering::AcqRel, Ordering::Acquire)
492 .is_ok()
493 {
494 return true;
495 }
496 }
497 }
498
499 pub fn available(&self) -> u64 {
501 self.refill();
502 self.tokens.load(Ordering::Acquire)
503 }
504
505 fn refill(&self) {
506 let mut last_refill = self.last_refill.lock();
507 let now = Instant::now();
508 let elapsed = now.duration_since(*last_refill).as_secs_f64();
509
510 if elapsed > 0.0 {
511 let new_tokens = (elapsed * self.refill_rate) as u64;
512 if new_tokens > 0 {
513 let current = self.tokens.load(Ordering::Acquire);
514 let new_value = (current + new_tokens).min(self.capacity);
515 self.tokens.store(new_value, Ordering::Release);
516 *last_refill = now;
517 }
518 }
519 }
520}
521
522#[cfg(test)]
523mod tests {
524 use super::*;
525
526 #[test]
527 fn test_generate_uuid() {
528 let uuid1 = generate_uuid();
529 let uuid2 = generate_uuid();
530 assert_ne!(uuid1, uuid2);
531 assert_eq!(uuid1.len(), 36);
532 }
533
534 #[test]
535 fn test_generate_sequential_id() {
536 let id1 = generate_sequential_id("device");
537 let id2 = generate_sequential_id("device");
538 assert!(id1.starts_with("device-"));
539 assert!(id2.starts_with("device-"));
540 assert_ne!(id1, id2);
541 }
542
543 #[test]
544 fn test_format_duration() {
545 assert_eq!(format_duration(Duration::from_secs(3661)), "1h 1m");
546 assert_eq!(format_duration(Duration::from_secs(61)), "1m 1s");
547 assert_eq!(format_duration(Duration::from_millis(1500)), "1.500s");
548 assert_eq!(format_duration(Duration::from_millis(500)), "500ms");
549 assert_eq!(format_duration(Duration::from_micros(500)), "500µs");
550 }
551
552 #[test]
553 fn test_format_bytes() {
554 assert_eq!(format_bytes(500), "500 B");
555 assert_eq!(format_bytes(1024), "1.00 KB");
556 assert_eq!(format_bytes(1536), "1.50 KB");
557 assert_eq!(format_bytes(1048576), "1.00 MB");
558 assert_eq!(format_bytes(1073741824), "1.00 GB");
559 }
560
561 #[test]
562 fn test_truncate_string() {
563 assert_eq!(truncate_string("hello", 10), "hello");
564 assert_eq!(truncate_string("hello world", 8), "hello...");
565 }
566
567 #[test]
568 fn test_sanitize_identifier() {
569 assert_eq!(sanitize_identifier("hello world!"), "hello_world_");
570 assert_eq!(sanitize_identifier("device-001"), "device-001");
571 }
572
573 #[test]
574 fn test_stopwatch() {
575 let mut sw = Stopwatch::start();
576 std::thread::sleep(Duration::from_millis(10));
577 sw.lap("step1");
578 assert!(sw.elapsed().as_millis() >= 10);
579 assert_eq!(sw.laps().len(), 1);
580 }
581
582 #[test]
583 fn test_retry_config() {
584 let config = RetryConfig::new(3);
585 assert_eq!(config.delay_for_attempt(0), Duration::ZERO);
586 assert_eq!(config.delay_for_attempt(1), Duration::from_millis(100));
587 assert_eq!(config.delay_for_attempt(2), Duration::from_millis(200));
588 }
589
590 #[test]
591 fn test_rate_limiter() {
592 let limiter = RateLimiter::new(10, 100.0);
593 assert!(limiter.try_acquire());
594 assert_eq!(limiter.available(), 9);
595
596 for _ in 0..9 {
598 assert!(limiter.try_acquire());
599 }
600 assert!(!limiter.try_acquire());
601 }
602}