use crate::infrastructure::persistence::smtp_selection_repository::{
SmtpEndpoint, SmtpSelectionRepository,
};
#[derive(Debug, thiserror::Error)]
pub enum MailServerQueryError {
#[error("db: {0}")]
Db(#[from] sqlx::Error),
#[error("invalid sender address: {0}")]
Invalid(String),
}
pub struct MailServerQueryService {
pool: sqlx::PgPool,
}
impl MailServerQueryService {
pub fn new(pool: sqlx::PgPool) -> Self {
Self { pool }
}
pub async fn resolve_endpoint(
&self,
from_email: &str,
) -> Result<Option<SmtpEndpoint>, MailServerQueryError> {
let (local_part, domain) = split_address(from_email)
.ok_or_else(|| MailServerQueryError::Invalid(from_email.to_string()))?;
let conn = &mut self.pool.acquire().await?;
SmtpSelectionRepository::resolve_endpoint(conn, &local_part, &domain)
.await
.map_err(Into::into)
}
}
pub fn split_address(address: &str) -> Option<(String, String)> {
let idx = address.rfind('@')?;
let local = address[..idx].trim().to_ascii_lowercase();
let domain = address[idx + 1..].trim().to_ascii_lowercase();
if local.is_empty() || domain.is_empty() {
return None;
}
Some((local, domain))
}
#[cfg(test)]
mod tests {
use super::split_address;
#[test]
fn splits_on_last_at_and_lowercases() {
assert_eq!(
split_address("\"a@b\"@Example.COM"),
Some(("\"a@b\"".into(), "example.com".into()))
);
}
#[test]
fn domainless_address_is_none() {
assert_eq!(split_address("no-domain"), None);
assert_eq!(split_address("@example.com"), None);
assert_eq!(split_address("user@"), None);
}
}