1use std::error::Error;
2
3use async_trait::async_trait;
4use hickory_resolver::{config::ResolverConfig, net::runtime::TokioRuntimeProvider};
5
6#[async_trait]
8pub trait DnsProvider {
9 async fn set_acme_txt_record(
10 &self,
11 acme_challenge_identifier: &str,
12 dns_value: &str,
13 ) -> Result<(), Box<dyn Error + Send + Sync>>;
14
15 #[allow(unused_variables)]
16 async fn remove_acme_txt_record(&self, acme_challenge_identifier: &str) -> Result<(), Box<dyn Error + Send + Sync>> {
17 Ok(())
18 }
19}
20
21pub async fn separate_subdomain_from_domain_name(domain_name: &str) -> (String, String) {
23 let parts: Vec<&str> = domain_name
24 .strip_suffix(".")
25 .unwrap_or(domain_name)
26 .split('.')
27 .collect();
28 let Ok(resolver) = hickory_resolver::Resolver::builder_tokio()
29 .unwrap_or(hickory_resolver::Resolver::builder_with_config(
30 ResolverConfig::default(),
31 TokioRuntimeProvider::default(),
32 ))
33 .build()
34 else {
35 return ("".to_string(), domain_name.to_string());
36 };
37
38 for parts_index in 0..parts.len() {
39 if resolver
40 .soa_lookup(format!("{}.", parts[parts_index..].join(".")))
41 .await
42 .is_ok()
43 {
44 let subdomain = parts[..parts_index].join(".");
46 let domain = parts[parts_index..].join(".");
47 return (subdomain, domain);
48 }
49 }
50
51 ("".to_string(), parts.join("."))
52}