use std::{
error::Error,
net::{IpAddr, Ipv4Addr, Ipv6Addr},
str::FromStr,
};
use serde::Deserialize;
use strum::IntoStaticStr;
use crate::{ContentCreationError, domain::Domain};
#[derive(Debug, Deserialize, PartialEq, Eq, IntoStaticStr)]
#[serde(rename_all = "UPPERCASE")]
#[strum(serialize_all = "UPPERCASE")]
pub enum Type {
A,
Mx,
Cname,
Alias,
Txt,
Ns,
Aaaa,
Srv,
Tlsa,
Caa,
Https,
Svcb,
}
impl Type {
#[must_use]
pub fn as_str(&self) -> &'static str {
self.into()
}
}
impl From<&Content> for Type {
fn from(value: &Content) -> Self {
match value {
Content::A(_) => Type::A,
Content::Mx(_) => Type::Mx,
Content::Cname(_) => Type::Cname,
Content::Alias(_) => Type::Alias,
Content::Txt(_) => Type::Txt,
Content::Ns(_) => Type::Ns,
Content::Aaaa(_) => Type::Aaaa,
Content::Srv(_) => Type::Srv,
Content::Tlsa(_) => Type::Tlsa,
Content::Caa(_) => Type::Caa,
Content::Https(_) => Type::Https,
Content::Svcb(_) => Type::Svcb,
}
}
}
#[derive(Debug, PartialEq, Eq)]
pub enum Content {
A(Ipv4Addr),
Mx(String),
Cname(String),
Alias(String),
Txt(String),
Ns(String),
Aaaa(Ipv6Addr),
Srv(String),
Tlsa(String),
Caa(String),
Https(String),
Svcb(String),
}
impl Content {
#[must_use]
pub fn type_as_str(&self) -> &'static str {
Type::from(self).into()
}
#[must_use]
pub fn value_to_string(&self) -> String {
match self {
Content::A(addr) => addr.to_string(),
Content::Aaaa(addr) => addr.to_string(),
Content::Mx(value)
| Content::Cname(value)
| Content::Alias(value)
| Content::Txt(value)
| Content::Ns(value)
| Content::Srv(value)
| Content::Tlsa(value)
| Content::Caa(value)
| Content::Https(value)
| Content::Svcb(value) => value.clone(),
}
}
pub fn from(type_: &Type, content: &str) -> Result<Content, ContentCreationError> {
Ok(match type_ {
Type::A => Content::A(content.parse()?),
Type::Mx => Content::Mx(content.to_owned()),
Type::Cname => Content::Cname(content.to_owned()),
Type::Alias => Content::Alias(content.to_owned()),
Type::Txt => Content::Txt(content.to_owned()),
Type::Ns => Content::Ns(content.to_owned()),
Type::Aaaa => Content::Aaaa(content.parse()?),
Type::Srv => Content::Srv(content.to_owned()),
Type::Tlsa => Content::Tlsa(content.to_owned()),
Type::Caa => Content::Caa(content.to_owned()),
Type::Https => Content::Https(content.to_owned()),
Type::Svcb => Content::Svcb(content.to_owned()),
})
}
}
impl From<IpAddr> for Content {
fn from(value: IpAddr) -> Self {
match value {
IpAddr::V4(addr) => Content::A(addr),
IpAddr::V6(addr) => Content::Aaaa(addr),
}
}
}
impl<'de> Deserialize<'de> for Content {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
use serde::de::Error as _;
#[derive(Deserialize)]
struct ContentDeserializable {
#[serde(rename = "type")]
type_: Type,
content: String,
}
let c = ContentDeserializable::deserialize(deserializer)?;
Content::from(&c.type_, &c.content).map_err(D::Error::custom)
}
}
#[derive(Debug, Deserialize)]
pub struct Record {
#[serde(deserialize_with = "deserialize_string_or_t")]
pub id: i64,
pub name: Box<Domain>,
#[serde(flatten)]
pub content: Content,
#[serde(deserialize_with = "deserialize_string_or_t")]
pub ttl: i64,
#[serde(deserialize_with = "deserialize_option_string_or_t")]
pub prio: Option<i64>,
pub notes: Option<String>,
}
#[derive(Deserialize)]
#[serde(untagged)]
enum StringOr<T> {
String(String),
T(T),
}
pub(crate) fn deserialize_string_or_t<'de, D, T: FromStr + Deserialize<'de>>(
deserializer: D,
) -> Result<T, D::Error>
where
D: serde::Deserializer<'de>,
<T as FromStr>::Err: Error,
{
use serde::de::Error as _;
let string_or_t = StringOr::<T>::deserialize(deserializer)?;
Ok(match string_or_t {
StringOr::<T>::String(s) => s.parse().map_err(D::Error::custom)?,
StringOr::<T>::T(t) => t,
})
}
pub(crate) fn deserialize_option_string_or_t<'de, D, T: FromStr + Deserialize<'de>>(
deserializer: D,
) -> Result<Option<T>, D::Error>
where
D: serde::Deserializer<'de>,
<T as FromStr>::Err: Error,
{
use serde::de::Error as _;
let string_or_i64 = Option::<StringOr<T>>::deserialize(deserializer)?;
Ok(match string_or_i64 {
Some(StringOr::<T>::String(s)) => Some(s.parse().map_err(D::Error::custom)?),
Some(StringOr::<T>::T(t)) => Some(t),
None => None,
})
}