#![allow(async_fn_in_trait)] use crate::cloudflare::CloudflareDnsProvider; use derive_more::Display; use std::fmt::Debug; use std::str::FromStr; use std::sync::Arc; use tokio::sync::RwLock; pub mod cloudflare; #[derive(Clone, Debug)] pub struct MicroTld { base_url: String, provider: DnsProviderType, } impl MicroTld { pub fn new(base_url: String, provider: DnsProviderType) -> anyhow::Result { Ok(Self { base_url, provider, }) } pub fn get_base_url(&self) -> &str { &self.base_url } pub fn get_provider_wrapped(&mut self) -> &mut DnsProviderType { &mut self.provider } } #[derive(Debug, Display, Copy, Clone, PartialEq, Eq, Hash)] pub enum RecordType { A, AAAA, CNAME, MX, TXT, SRV, NS, CAA, PTR, DNSKEY, DS, HTTPS, LOC, NAPTR, SMIMEA, SSHFP, SVCB, TLSA, URI, } impl FromStr for RecordType { type Err = anyhow::Error; fn from_str(s: &str) -> Result { Ok(match s { "A" => RecordType::A, "AAAA" => RecordType::AAAA, "CNAME" => RecordType::CNAME, "MX" => RecordType::MX, "TXT" => RecordType::TXT, "SRV" => RecordType::SRV, "NS" => RecordType::NS, "CAA" => RecordType::CAA, "PTR" => RecordType::PTR, "DNSKEY" => RecordType::DNSKEY, "DS" => RecordType::DS, "HTTPS" => RecordType::HTTPS, "LOC" => RecordType::LOC, "NAPTR" => RecordType::NAPTR, "SMIMEA" => RecordType::SMIMEA, "SSHFP" => RecordType::SSHFP, "SVCB" => RecordType::SVCB, "TLSA" => RecordType::TLSA, "URI" => RecordType::URI, _ => return Err(anyhow::anyhow!("Unsupported record type: {}", s)), }) } } pub trait DnsRecord<'a> where Self: Sized { type Client; type Provider: DnsProvider<'a>; fn get_provider(&self) -> &Self::Provider; async fn name(&mut self) -> anyhow::Result; async fn target(&mut self) -> anyhow::Result; async fn ttl(&mut self) -> anyhow::Result; async fn record_type(&mut self) -> anyhow::Result; async fn is_valid(&self) -> anyhow::Result; async fn priority(&mut self) -> anyhow::Result>; async fn set_target(&mut self, ip: &str) -> anyhow::Result<()>; async fn set_ttl(&mut self, ttl: u32) -> anyhow::Result<()>; async fn set_record_type(&mut self, record_type: RecordType) -> anyhow::Result<()>; async fn set_priority(&mut self, priority: u16) -> anyhow::Result<()>; async fn delete(&mut self) -> anyhow::Result<()>; async fn refresh(&mut self) -> anyhow::Result<()>; } pub trait DnsProvider<'a> where Self: Sized { type Client; type Record: DnsRecord<'a, Client = Self::Client>; fn init_client(&mut self) -> anyhow::Result<()>; fn get_client(&self) -> Result<&Self::Client, ClientNotInitializedError>; async fn get_dns_records_by_name(&'a self, name: impl Into) -> anyhow::Result>>; async fn get_dns_records_by_ip(&'a self, ip: impl Into) -> anyhow::Result>>; async fn get_dns_records(&'a self) -> anyhow::Result>>; async fn get_dns_record_by_id(&'a self, id: &str) -> anyhow::Result; async fn create_dns_record(&'a self, subdomain: &str, target: &str, record_type: RecordType, ttl: Option) -> anyhow::Result; } #[derive(Clone, Debug)] pub enum DnsProviderType { Cloudflare(Arc>), } impl DnsProviderType { pub fn cloudflare(api_key: String, zone_id: String) -> anyhow::Result { Ok(Self::Cloudflare(Arc::new(RwLock::new(CloudflareDnsProvider::new(api_key, zone_id)?)))) } pub fn as_provider(&mut self) -> &mut Arc>> { match self { Self::Cloudflare(provider) => provider, } } } #[derive(Debug, Display)] #[display("Client not initialized: {}", message)] pub struct ClientNotInitializedError { message: String, } impl std::error::Error for ClientNotInitializedError {} #[derive(Debug, Display)] #[display("DNS record does not exist: {}", record_name)] pub struct RecordDoesNotExistError { record_name: String, } impl std::error::Error for RecordDoesNotExistError {}