use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum RecordType {
A,
AAAA,
CNAME,
MX,
TXT,
SRV,
NS,
CAA,
PTR,
DNSKEY,
DS,
HTTPS,
LOC,
NAPTR,
SMIMEA,
SSHFP,
SVCB,
TLSA,
URI,
}
impl RecordType {
pub fn as_str(&self) -> &'static str {
match self {
RecordType::A => "A",
RecordType::AAAA => "AAAA",
RecordType::CNAME => "CNAME",
RecordType::MX => "MX",
RecordType::TXT => "TXT",
RecordType::SRV => "SRV",
RecordType::NS => "NS",
RecordType::CAA => "CAA",
RecordType::PTR => "PTR",
RecordType::DNSKEY => "DNSKEY",
RecordType::DS => "DS",
RecordType::HTTPS => "HTTPS",
RecordType::LOC => "LOC",
RecordType::NAPTR => "NAPTR",
RecordType::SMIMEA => "SMIMEA",
RecordType::SSHFP => "SSHFP",
RecordType::SVCB => "SVCB",
RecordType::TLSA => "TLSA",
RecordType::URI => "URI",
}
}
}
impl std::fmt::Display for RecordType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.as_str())
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DnsRecord {
pub id: String,
#[serde(rename = "type")]
pub record_type: String,
pub name: String,
pub content: String,
pub proxied: bool,
pub ttl: u32,
#[serde(skip_serializing_if = "Option::is_none")]
pub zone_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub zone_name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub created_on: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub modified_on: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub comment: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub priority: Option<u16>,
}
impl DnsRecord {
pub fn matches(&self, name: &str, record_type: RecordType) -> bool {
self.name == name && self.record_type == record_type.as_str()
}
pub fn needs_update(&self, desired: &DnsRecordBuilder) -> bool {
if let Some(content) = &desired.content
&& &self.content != content
{
return true;
}
if let Some(proxied) = desired.proxied
&& self.proxied != proxied
{
return true;
}
if let Some(ttl) = desired.ttl
&& self.ttl != ttl
{
return true;
}
false
}
}
#[derive(Debug, Clone, Default)]
pub struct DnsRecordBuilder {
pub(crate) name: Option<String>,
pub(crate) record_type: Option<RecordType>,
pub(crate) content: Option<String>,
pub(crate) proxied: Option<bool>,
pub(crate) ttl: Option<u32>,
pub(crate) comment: Option<String>,
pub(crate) priority: Option<u16>,
}
impl DnsRecordBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn name(mut self, name: impl Into<String>) -> Self {
self.name = Some(name.into());
self
}
pub fn record_type(mut self, record_type: RecordType) -> Self {
self.record_type = Some(record_type);
self
}
pub fn content(mut self, content: impl Into<String>) -> Self {
self.content = Some(content.into());
self
}
pub fn proxied(mut self, proxied: bool) -> Self {
self.proxied = Some(proxied);
self
}
pub fn ttl(mut self, ttl: u32) -> Self {
self.ttl = Some(ttl);
self
}
pub fn comment(mut self, comment: impl Into<String>) -> Self {
self.comment = Some(comment.into());
self
}
pub fn priority(mut self, priority: u16) -> Self {
self.priority = Some(priority);
self
}
pub(crate) fn build_payload(&self) -> serde_json::Value {
let mut payload = serde_json::json!({});
if let Some(name) = &self.name {
payload["name"] = serde_json::Value::String(name.clone());
}
if let Some(record_type) = &self.record_type {
payload["type"] = serde_json::Value::String(record_type.as_str().to_string());
}
if let Some(content) = &self.content {
payload["content"] = serde_json::Value::String(content.clone());
}
if let Some(proxied) = self.proxied {
payload["proxied"] = serde_json::Value::Bool(proxied);
}
if let Some(ttl) = self.ttl {
payload["ttl"] = serde_json::Value::Number(ttl.into());
}
if let Some(comment) = &self.comment {
payload["comment"] = serde_json::Value::String(comment.clone());
}
if let Some(priority) = self.priority {
payload["priority"] = serde_json::Value::Number(priority.into());
}
payload
}
pub(crate) fn validate_create(&self) -> Result<(), crate::error::Error> {
if self.name.is_none() {
return Err(crate::error::Error::InvalidInput(
"DNS record name is required".to_string(),
));
}
if self.record_type.is_none() {
return Err(crate::error::Error::InvalidInput(
"DNS record type is required".to_string(),
));
}
if self.content.is_none() {
return Err(crate::error::Error::InvalidInput(
"DNS record content is required".to_string(),
));
}
Ok(())
}
}
#[derive(Debug, Clone, Default)]
pub struct ListRecordsQuery {
pub(crate) record_type: Option<RecordType>,
pub(crate) name: Option<String>,
pub(crate) content: Option<String>,
pub(crate) page: Option<u32>,
pub(crate) per_page: Option<u32>,
}
impl ListRecordsQuery {
pub fn new() -> Self {
Self::default()
}
pub fn record_type(mut self, record_type: RecordType) -> Self {
self.record_type = Some(record_type);
self
}
pub fn name(mut self, name: impl Into<String>) -> Self {
self.name = Some(name.into());
self
}
pub fn content(mut self, content: impl Into<String>) -> Self {
self.content = Some(content.into());
self
}
pub fn page(mut self, page: u32) -> Self {
self.page = Some(page);
self
}
pub fn per_page(mut self, per_page: u32) -> Self {
self.per_page = Some(per_page);
self
}
pub(crate) fn build_params(&self) -> Vec<(&str, String)> {
let mut params = Vec::new();
if let Some(record_type) = &self.record_type {
params.push(("type", record_type.as_str().to_string()));
}
if let Some(name) = &self.name {
params.push(("name", name.clone()));
}
if let Some(content) = &self.content {
params.push(("content", content.clone()));
}
if let Some(page) = self.page {
params.push(("page", page.to_string()));
}
if let Some(per_page) = self.per_page {
params.push(("per_page", per_page.to_string()));
}
params
}
}