use std::fmt;
use async_trait::async_trait;
pub mod challtestsrv;
pub mod cloudflare;
pub mod route53;
pub use challtestsrv::ChallTestSrvDns;
pub use cloudflare::CloudflareDns;
pub use route53::Route53Dns;
#[async_trait]
#[allow(clippy::double_must_use)]
pub trait DnsProvider: Send + Sync {
async fn add_txt(&self, name: &str, value: &str) -> Result<TxtHandle, DnsError>;
async fn remove_txt(&self, handle: &TxtHandle) -> Result<(), DnsError>;
}
#[derive(Debug, Clone)]
pub struct TxtHandle {
pub name: String,
pub value: String,
pub provider_ref: String,
}
#[derive(Debug)]
pub enum DnsError {
Api(String),
Config(String),
}
impl fmt::Display for DnsError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Api(m) => write!(f, "DNS provider API error: {m}"),
Self::Config(m) => write!(f, "DNS provider config error: {m}"),
}
}
}
impl std::error::Error for DnsError {}
#[cfg(test)]
mod tests {
use std::sync::Mutex;
use super::*;
#[derive(Default)]
struct MemDns {
records: Mutex<Vec<(String, String, String)>>,
next_id: Mutex<u64>,
}
impl MemDns {
fn values_at(&self, name: &str) -> Vec<String> {
self.records
.lock()
.unwrap()
.iter()
.filter(|(_, n, _)| n == name)
.map(|(_, _, v)| v.clone())
.collect()
}
}
#[async_trait]
impl DnsProvider for MemDns {
async fn add_txt(&self, name: &str, value: &str) -> Result<TxtHandle, DnsError> {
let mut id = self.next_id.lock().unwrap();
*id += 1;
let rec_id = id.to_string();
self.records.lock().unwrap().push((
rec_id.clone(),
name.to_string(),
value.to_string(),
));
Ok(TxtHandle {
name: name.to_string(),
value: value.to_string(),
provider_ref: rec_id,
})
}
async fn remove_txt(&self, handle: &TxtHandle) -> Result<(), DnsError> {
self.records.lock().unwrap().retain(|(id, _, _)| id != &handle.provider_ref);
Ok(())
}
}
#[tokio::test]
async fn two_values_on_one_name_coexist_and_remove_is_per_record() {
let dns = MemDns::default();
let name = "_acme-challenge.example.com";
let h_apex = dns.add_txt(name, "apex-token-value").await.unwrap();
let h_wild = dns.add_txt(name, "wildcard-token-value").await.unwrap();
let mut values = dns.values_at(name);
values.sort();
assert_eq!(values, vec!["apex-token-value", "wildcard-token-value"]);
assert_ne!(h_apex.provider_ref, h_wild.provider_ref);
dns.remove_txt(&h_apex).await.unwrap();
assert_eq!(dns.values_at(name), vec!["wildcard-token-value"]);
dns.remove_txt(&h_apex).await.unwrap();
dns.remove_txt(&h_wild).await.unwrap();
assert!(dns.values_at(name).is_empty());
}
}