use crate::error::{Error, Result};
use std::time::{Duration, Instant};
pub fn validate_identifier(kind: &str, name: &str) -> Result<()> {
if name.is_empty() {
return Err(Error::Config(format!(
"{kind} identifier must not be empty"
)));
}
let mut chars = name.chars();
let first = chars.next().unwrap();
if !first.is_ascii_alphabetic() && first != '_' {
return Err(Error::Config(format!(
"{kind} identifier '{name}' must start with a letter or underscore"
)));
}
if !chars.all(|c| c.is_ascii_alphanumeric() || c == '_') {
return Err(Error::Config(format!(
"{kind} identifier '{name}' contains invalid characters"
)));
}
Ok(())
}
pub fn quote_ident(kind: &str, name: &str) -> Result<String> {
validate_identifier(kind, name)?;
Ok(format!("\"{}\"", name.replace('"', "\"\"")))
}
pub fn amqp_url_with_credentials(url: &str, username: &str, password: &str) -> Result<String> {
if username.is_empty() && password.is_empty() {
return Ok(url.to_string());
}
let mut parsed = url::Url::parse(url)?;
if !username.is_empty() {
parsed
.set_username(username)
.map_err(|_| Error::Config("AMQP URL cannot accept configured username".to_string()))?;
}
if !password.is_empty() {
parsed
.set_password(Some(password))
.map_err(|_| Error::Config("AMQP URL cannot accept configured password".to_string()))?;
}
Ok(parsed.to_string())
}
pub fn cancel_signal_ttl(config: &crate::config::Config) -> Duration {
Duration::from_secs(config.global.cancel_signal_ttl_secs.max(1))
}
pub fn prune_expired_cancel_signals(signals: &dashmap::DashMap<String, Instant>, ttl: Duration) {
signals.retain(|_, inserted_at| inserted_at.elapsed() <= ttl);
}