use anyhow::{bail, Result};
use std::time::{Duration, Instant};
use tracing::warn;
pub struct RateLimiter {
min_interval: Duration,
last_request: Option<Instant>,
}
impl RateLimiter {
pub fn new(min_interval: Duration) -> Self {
Self {
min_interval,
last_request: None,
}
}
pub fn check_and_update(&mut self) -> bool {
match self.last_request {
None => {
self.last_request = Some(Instant::now());
true
}
Some(last) => {
if last.elapsed() >= self.min_interval {
self.last_request = Some(Instant::now());
true
} else {
false
}
}
}
}
pub async fn wait_if_needed(&mut self) {
if !self.check_and_update() {
if let Some(last) = self.last_request {
let elapsed = last.elapsed();
let wait_time = self.min_interval.saturating_sub(elapsed);
if wait_time.as_millis() > 0 {
tokio::time::sleep(wait_time).await;
}
}
}
}
}
pub struct SensitiveData;
impl SensitiveData {
pub fn mask_key(key_b64: &str) -> String {
if key_b64.len() <= 8 {
"***".to_string()
} else {
format!(
"{}...{}",
&key_b64[..4],
&key_b64[key_b64.len() - 4..]
)
}
}
pub fn validate_url_scheme(url: &str) -> Result<()> {
if !url.starts_with("https://") {
warn!("URL does not use HTTPS: {}", url);
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_mask_key() {
let key = "abcdefghijklmnop";
let masked = SensitiveData::mask_key(key);
assert_eq!(masked, "abcd...mnop");
let short_key = "abc";
let masked_short = SensitiveData::mask_key(short_key);
assert_eq!(masked_short, "***");
}
#[test]
fn test_rate_limiter() {
let mut limiter = RateLimiter::new(Duration::from_millis(100));
assert!(limiter.check_and_update());
assert!(!limiter.check_and_update()); }
#[tokio::test]
async fn test_rate_limiter_async() {
let mut limiter = RateLimiter::new(Duration::from_millis(50));
assert!(limiter.check_and_update());
assert!(!limiter.check_and_update());
limiter.wait_if_needed().await; assert!(limiter.check_and_update());
}
#[test]
fn test_validate_url() {
assert!(SensitiveData::validate_url_scheme("https://example.com").is_ok());
assert!(SensitiveData::validate_url_scheme("http://example.com").is_ok());
}
}