microtld/backend/src/lib.rs
2026-08-21 12:24:52 +02:00

151 lines
4.5 KiB
Rust

#![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<Self> {
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<Self, Self::Err> {
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<String>;
async fn target(&mut self) -> anyhow::Result<String>;
async fn ttl(&mut self) -> anyhow::Result<u32>;
async fn record_type(&mut self) -> anyhow::Result<RecordType>;
async fn is_valid(&self) -> anyhow::Result<bool>;
async fn priority(&mut self) -> anyhow::Result<Option<u16>>;
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<String>) -> anyhow::Result<impl IntoIterator<Item = anyhow::Result<Self::Record>>>;
async fn get_dns_records_by_ip(&'a self, ip: impl Into<String>) -> anyhow::Result<impl IntoIterator<Item = anyhow::Result<Self::Record>>>;
async fn get_dns_records(&'a self) -> anyhow::Result<impl IntoIterator<Item = anyhow::Result<Self::Record>>>;
async fn get_dns_record_by_id(&'a self, id: &str) -> anyhow::Result<Self::Record>;
async fn create_dns_record(&'a self, subdomain: &str, target: &str, record_type: RecordType, ttl: Option<u32>) -> anyhow::Result<Self::Record>;
}
#[derive(Clone, Debug)]
pub enum DnsProviderType {
Cloudflare(Arc<RwLock<CloudflareDnsProvider>>),
}
impl DnsProviderType {
pub fn cloudflare(api_key: String, zone_id: String) -> anyhow::Result<Self> {
Ok(Self::Cloudflare(Arc::new(RwLock::new(CloudflareDnsProvider::new(api_key, zone_id)?))))
}
pub fn as_provider(&mut self) -> &mut Arc<RwLock<impl DnsProvider<'_>>> {
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 {}