65 lines
1.8 KiB
Rust
65 lines
1.8 KiB
Rust
use std::sync::Arc;
|
|
use std::time::Duration;
|
|
|
|
use tokio::sync::Mutex;
|
|
use tokio::time::Instant;
|
|
|
|
/// A simple rate limiter that enforces a minimum interval between requests.
|
|
/// Can be cloned (via Arc) to share across multiple clients.
|
|
#[derive(Clone)]
|
|
pub struct RateLimiter {
|
|
last_request: Arc<Mutex<Instant>>,
|
|
interval: Duration,
|
|
}
|
|
|
|
impl RateLimiter {
|
|
pub fn new(interval: Duration) -> Self {
|
|
Self {
|
|
last_request: Arc::new(Mutex::new(Instant::now() - interval)),
|
|
interval,
|
|
}
|
|
}
|
|
|
|
/// Wait if needed so we don't exceed the rate limit.
|
|
pub async fn wait(&self) {
|
|
let mut last = self.last_request.lock().await;
|
|
let elapsed = last.elapsed();
|
|
if elapsed < self.interval {
|
|
tokio::time::sleep(self.interval - elapsed).await;
|
|
}
|
|
*last = Instant::now();
|
|
}
|
|
}
|
|
|
|
/// Build a reqwest client with a custom user agent and timeout.
|
|
pub fn build_client(user_agent: &str, timeout_secs: u64) -> reqwest::Result<reqwest::Client> {
|
|
reqwest::Client::builder()
|
|
.user_agent(user_agent)
|
|
.timeout(Duration::from_secs(timeout_secs))
|
|
.build()
|
|
}
|
|
|
|
/// Simple URL-encode for query parameters.
|
|
pub fn urlencoded(s: &str) -> String {
|
|
s.replace(' ', "+")
|
|
.replace('&', "%26")
|
|
.replace('=', "%3D")
|
|
.replace('#', "%23")
|
|
}
|
|
|
|
/// Escape special characters for MusicBrainz Lucene query syntax.
|
|
pub fn escape_lucene(s: &str) -> String {
|
|
let special = [
|
|
'+', '-', '&', '|', '!', '(', ')', '{', '}', '[', ']', '^', '"', '~', '*', '?', ':', '\\',
|
|
'/',
|
|
];
|
|
let mut result = String::with_capacity(s.len());
|
|
for c in s.chars() {
|
|
if special.contains(&c) {
|
|
result.push('\\');
|
|
}
|
|
result.push(c);
|
|
}
|
|
result
|
|
}
|