diff --git a/src/database.rs b/src/database.rs index ef3ba34..4627679 100644 --- a/src/database.rs +++ b/src/database.rs @@ -1,6 +1,6 @@ use std::sync::Arc; -use crate::ressouces::zone::ZoneModel; +use crate::resources::zone::ZoneModel; pub trait Db: ZoneModel + Send + Sync {} pub type BoxedDb = Arc; diff --git a/src/dns/dns_driver.rs b/src/dns/dns_driver.rs index 1a1da84..402826d 100644 --- a/src/dns/dns_driver.rs +++ b/src/dns/dns_driver.rs @@ -11,7 +11,8 @@ use domain::tsig::{Algorithm, Key, KeyName}; use domain::net::client::request::{self, RequestMessage, RequestMessageMulti, SendRequest, SendRequestMulti}; use tokio::net::TcpStream; -use crate::ressouces::{rdata, record}; +use crate::resources::record; +use crate::proto; use super::{RecordDriver, ZoneDriver, DnsDriverError}; use async_trait::async_trait; @@ -162,7 +163,7 @@ impl RecordDriver for DnsDriver { let answer = reply.answer()?; - for record in answer.limit_to::>() { + for record in answer.limit_to::>() { let record = record?; records.push(record.into()) } diff --git a/src/dns/mod.rs b/src/dns/mod.rs index 624cac8..1db0d65 100644 --- a/src/dns/mod.rs +++ b/src/dns/mod.rs @@ -4,7 +4,7 @@ use std::sync::Arc; use async_trait::async_trait; -use crate::ressouces::record; +use crate::resources::record; pub type BoxedZoneDriver = Arc; pub type BoxedRecordDriver = Arc; diff --git a/src/errors.rs b/src/errors.rs index 4d6ab4f..d2b475f 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -8,10 +8,11 @@ use serde::{Serialize, Serializer}; use serde_json::{Value, json}; use crate::dns::DnsDriverError; -use crate::ressouces::record::{RecordError, RecordParseError}; -use crate::ressouces::zone::ZoneError; +use crate::resources::record::{RecordError, RecordParseError}; +use crate::resources::zone::ZoneError; use crate::validation::{DomainValidationError, TxtParseError}; use crate::template::TemplateError; +use crate::proto::dns::ProtoDnsError; #[derive(Debug, Serialize)] pub struct Error { @@ -387,3 +388,18 @@ impl From for Error { } } } + +impl From for Error { + fn from(value: ProtoDnsError) -> Self { + match value { + ProtoDnsError::RDataUnknown { input, field, rtype } => { + Error::new("proto:dns:rdata_unknown", "Unknown error while converting internal rdata to proto rdata") + .with_details(json!({ + "input": input, + "field": field, + "rtype": rtype, + })) + }, + } + } +} diff --git a/src/main.rs b/src/main.rs index f6c4982..a272ffc 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,11 +1,12 @@ mod errors; mod dns; mod routes; -mod ressouces; +mod resources; mod database; mod validation; mod macros; mod template; +mod proto; use std::sync::Arc; diff --git a/src/proto/dns.rs b/src/proto/dns.rs new file mode 100644 index 0000000..bc64b95 --- /dev/null +++ b/src/proto/dns.rs @@ -0,0 +1,425 @@ +use std::fmt::Write; + +use domain::base::rdata::ComposeRecordData; +use domain::base::wire::{Composer, ParseError}; +use domain::base::{Name, ParseRecordData, ParsedName, RecordData, Rtype, ToName, Ttl}; +use domain::rdata; +use domain::dep::octseq::{Parser, Octets}; + +use crate::resources::dns::internal; +use crate::errors::Error; + +pub enum ProtoDnsError { + RDataUnknown { input: String, field: String, rtype: String }, +} + +/* --------- A --------- */ + +impl From for internal::A { + fn from(record_data: domain::rdata::A) -> Self { + internal::A { address: record_data.addr() } + } +} + +impl TryFrom for domain::rdata::A { + type Error = Error; + + fn try_from(record_data: internal::A) -> Result { + Ok(domain::rdata::A::new(record_data.address)) + } +} + +/* --------- AAAA --------- */ + +impl From for internal::Aaaa { + fn from(record_data: domain::rdata::Aaaa) -> Self { + internal::Aaaa { address: record_data.addr() } + } +} + +impl TryFrom for domain::rdata::Aaaa { + type Error = Error; + + fn try_from(record_data: internal::Aaaa) -> Result { + Ok(domain::rdata::Aaaa::new(record_data.address)) + } +} + +/* --------- CNAME --------- */ + +impl From> for internal::Cname { + fn from(record_data: domain::rdata::Cname) -> Self { + internal::Cname { target: internal::Name::new(record_data.cname().to_string()) } + } +} + +impl TryFrom for domain::rdata::Cname>> { + type Error = Error; + + fn try_from(record_data: internal::Cname) -> Result>>, Self::Error> { + let cname = record_data.target.to_string(); + + let cname: Name<_> = cname.parse::>().map_err(|e| { + Error::from(ProtoDnsError::RDataUnknown { + input: cname, + field: "target".into(), + rtype: "CNAME".into(), + }).with_cause(&e.to_string()) + })?; + + Ok(domain::rdata::Cname::new(cname)) + } +} + +/* --------- MX --------- */ + +impl From> for internal::Mx { + fn from(record_data: domain::rdata::Mx) -> Self { + internal::Mx { + preference: record_data.preference(), + mail_exchanger: internal::Name::new(record_data.exchange().to_string()), + } + } +} + +impl TryFrom for domain::rdata::Mx>> { + type Error = Error; + + fn try_from(record_data: internal::Mx) -> Result>>, Self::Error> { + let mail_exchanger = record_data.mail_exchanger.to_string(); + + let mail_exchanger: Name<_> = mail_exchanger.parse::>().map_err(|e| { + Error::from(ProtoDnsError::RDataUnknown { + input: mail_exchanger, + field: "mail_exchanger".into(), + rtype: "MX".into(), + }).with_cause(&e.to_string()) + })?; + + Ok(domain::rdata::Mx::new(record_data.preference, mail_exchanger)) + } +} + +/* --------- NS --------- */ + +impl From> for internal::Ns { + fn from(record_data: domain::rdata::Ns) -> Self { + internal::Ns { + target: internal::Name::new(record_data.nsdname().to_string()), + } + } +} + +impl TryFrom for domain::rdata::Ns>> { + type Error = Error; + + fn try_from(record_data: internal::Ns) -> Result>>, Self::Error> { + let target = record_data.target.to_string(); + + let target: Name<_> = target.parse::>().map_err(|e| { + Error::from(ProtoDnsError::RDataUnknown { + input: target, + field: "target".into(), + rtype: "NS".into(), + }).with_cause(&e.to_string()) + })?; + + Ok(domain::rdata::Ns::new(target)) + } +} + +/* --------- PTR --------- */ + +impl From> for internal::Ptr { + fn from(record_data: domain::rdata::Ptr) -> Self { + internal::Ptr { + target: internal::Name::new(record_data.ptrdname().to_string()), + } + } +} + +impl TryFrom for domain::rdata::Ptr>> { + type Error = Error; + + fn try_from(record_data: internal::Ptr) -> Result>>, Self::Error> { + let target = record_data.target.to_string(); + + let target: Name<_> = target.parse::>().map_err(|e| { + Error::from(ProtoDnsError::RDataUnknown { + input: target, + field: "target".into(), + rtype: "PTR".into(), + }).with_cause(&e.to_string()) + })?; + + Ok(domain::rdata::Ptr::new(target)) + } +} + +/* --------- SOA --------- */ + +impl From> for internal::Soa { + fn from(record_rdata: domain::rdata::Soa) -> Self { + internal::Soa { + primary_server: internal::Name::new(record_rdata.mname().to_string()), + maintainer: internal::Name::new(record_rdata.rname().to_string()), + refresh: record_rdata.refresh().as_secs(), + retry: record_rdata.retry().as_secs(), + expire: record_rdata.expire().as_secs(), + minimum: record_rdata.minimum().as_secs(), + serial: record_rdata.serial().into(), + } + } +} + +impl TryFrom for domain::rdata::Soa>> { + type Error = Error; + + fn try_from(record_data: internal::Soa) -> Result>>, Self::Error> { + let primary_server = record_data.primary_server.to_string(); + let primary_server: Name<_> = primary_server.parse::>().map_err(|e| { + Error::from(ProtoDnsError::RDataUnknown { + input: primary_server, + field: "primary_server".into(), + rtype: "SOA".into(), + }).with_cause(&e.to_string()) + })?; + + let maintainer = record_data.maintainer.to_string(); + let maintainer: Name<_> = maintainer.parse::>().map_err(|e| { + Error::from(ProtoDnsError::RDataUnknown { + input: maintainer, + field: "maintainer".into(), + rtype: "SOA".into(), + }).with_cause(&e.to_string()) + })?; + + Ok(domain::rdata::Soa::new( + primary_server, + maintainer, + record_data.serial.into(), + Ttl::from_secs(record_data.refresh), + Ttl::from_secs(record_data.retry), + Ttl::from_secs(record_data.expire), + Ttl::from_secs(record_data.minimum), + )) + } +} + +/* --------- SRV --------- */ + +impl From> for internal::Srv { + fn from(record_data: domain::rdata::Srv) -> Self { + internal::Srv { + server: internal::Name::new(record_data.target().to_string()), + priority: record_data.priority(), + weight: record_data.weight(), + port: record_data.port(), + } + } +} + +impl TryFrom for domain::rdata::Srv>> { + type Error = Error; + + fn try_from(record_data: internal::Srv) -> Result>>, Self::Error> { + let server = record_data.server.to_string(); + let server: Name<_> = server.parse::>().map_err(|e| { + Error::from(ProtoDnsError::RDataUnknown { + input: server, + field: "server".into(), + rtype: "SRV".into(), + }).with_cause(&e.to_string()) + })?; + + + Ok(domain::rdata::Srv::new( + record_data.priority, + record_data.weight, + record_data.port, + server + )) + } +} + +/* --------- TXT --------- */ + +impl> From> for internal::Txt { + fn from(record_data: rdata::Txt) -> Self { + let concatenated_text: Vec<_> = record_data.iter() + .flatten() + .cloned() + .collect(); + + internal::Txt { + text: concatenated_text + } + } +} + +impl TryFrom for domain::rdata::Txt> { + type Error = Error; + + fn try_from(record_data: internal::Txt) -> Result>, Self::Error> { + let txt: domain::rdata::Txt<_> = rdata::Txt::build_from_slice(&record_data.text).map_err(|e| { + let text = record_data.text.iter() + .fold(String::new(), |mut output, b| { + write!(output, "{:02X}", b).unwrap(); + output + }); + + Error::from(ProtoDnsError::RDataUnknown { + input: format!("0x{}", text), + field: "text".into(), + rtype: "TXT".into(), + }).with_cause(&e.to_string()) + })?; + + + Ok(txt) + } +} + +/* --------- ParsedRData --------- */ + +pub enum ParsedRData { + A(domain::rdata::A), + Aaaa(domain::rdata::Aaaa), + Cname(domain::rdata::Cname), + Mx(domain::rdata::Mx), + Ns(domain::rdata::Ns), + Ptr(domain::rdata::Ptr), + Soa(domain::rdata::Soa), + Srv(domain::rdata::Srv), + Txt(domain::rdata::Txt), +} + +impl> From> for internal::RData { + fn from(value: ParsedRData) -> Self { + match value { + ParsedRData::A(record_rdata) => internal::RData::A(record_rdata.into()), + ParsedRData::Aaaa(record_rdata) => internal::RData::Aaaa(record_rdata.into()), + ParsedRData::Cname(record_rdata) => internal::RData::Cname(record_rdata.into()), + ParsedRData::Mx(record_rdata) => internal::RData::Mx(record_rdata.into()), + ParsedRData::Ns(record_rdata) => internal::RData::Ns(record_rdata.into()), + ParsedRData::Ptr(record_rdata) => internal::RData::Ptr(record_rdata.into()), + ParsedRData::Soa(record_rdata) => internal::RData::Soa(record_rdata.into()), + ParsedRData::Srv(record_rdata) => internal::RData::Srv(record_rdata.into()), + ParsedRData::Txt(record_rdata) => internal::RData::Txt(record_rdata.into()), + } + } +} + +impl TryFrom for ParsedRData>, Vec> { + type Error = Error; + + fn try_from(value: internal::RData) -> Result { + let rdata = match value { + internal::RData::A(record_rdata) => ParsedRData::A(record_rdata.try_into()?), + internal::RData::Aaaa(record_rdata) => ParsedRData::Aaaa(record_rdata.try_into()?), + internal::RData::Cname(record_rdata) => ParsedRData::Cname(record_rdata.try_into()?), + internal::RData::Mx(record_rdata) => ParsedRData::Mx(record_rdata.try_into()?), + internal::RData::Ns(record_rdata) => ParsedRData::Ns(record_rdata.try_into()?), + internal::RData::Ptr(record_rdata) => ParsedRData::Ptr(record_rdata.try_into()?), + internal::RData::Soa(record_rdata) => ParsedRData::Soa(record_rdata.try_into()?), + internal::RData::Srv(record_rdata) => ParsedRData::Srv(record_rdata.try_into()?), + internal::RData::Txt(record_rdata) => ParsedRData::Txt(record_rdata.try_into()?), + }; + Ok(rdata) + } +} + + +impl ParsedRData { + pub fn rtype(&self) -> Rtype { + match self { + ParsedRData::A(_) => Rtype::A, + ParsedRData::Aaaa(_) => Rtype::AAAA, + ParsedRData::Cname(_) => Rtype::CNAME, + ParsedRData::Mx(_) => Rtype::MX, + ParsedRData::Ns(_) => Rtype::NS, + ParsedRData::Ptr(_) => Rtype::PTR, + ParsedRData::Soa(_) => Rtype::SOA, + ParsedRData::Srv(_) => Rtype::SRV, + ParsedRData::Txt(_) => Rtype::TXT, + } + } +} + +impl RecordData for ParsedRData { + fn rtype(&self) -> Rtype { + ParsedRData::rtype(self) + } +} + +impl<'a, Octs: Octets + ?Sized> ParseRecordData<'a, Octs> for ParsedRData>, Octs::Range<'a>> { + fn parse_rdata( + rtype: Rtype, + parser: &mut Parser<'a, Octs>, + ) -> Result, ParseError> { + let record = match rtype { + Rtype::A => ParsedRData::A(rdata::A::parse(parser)?), + Rtype::AAAA => ParsedRData::Aaaa(rdata::Aaaa::parse(parser)?), + Rtype::CNAME => ParsedRData::Cname(rdata::Cname::parse(parser)?), + Rtype::MX => ParsedRData::Mx(rdata::Mx::parse(parser)?), + Rtype::NS => ParsedRData::Ns(rdata::Ns::parse(parser)?), + Rtype::PTR => ParsedRData::Ptr(rdata::Ptr::parse(parser)?), + Rtype::SOA => ParsedRData::Soa(rdata::Soa::parse(parser)?), + Rtype::SRV => ParsedRData::Srv(rdata::Srv::parse(parser)?), + Rtype::TXT => ParsedRData::Txt(rdata::Txt::parse(parser)?), + _ => return Ok(None) + }; + + Ok(Some(record)) + } +} + +impl> ComposeRecordData for ParsedRData { + fn rdlen(&self, compress: bool) -> Option { + match self { + ParsedRData::A(record_rdata) => record_rdata.rdlen(compress), + ParsedRData::Aaaa(record_rdata) => record_rdata.rdlen(compress), + ParsedRData::Cname(record_rdata) => record_rdata.rdlen(compress), + ParsedRData::Mx(record_rdata) => record_rdata.rdlen(compress), + ParsedRData::Ns(record_rdata) => record_rdata.rdlen(compress), + ParsedRData::Ptr(record_rdata) => record_rdata.rdlen(compress), + ParsedRData::Soa(record_rdata) => record_rdata.rdlen(compress), + ParsedRData::Srv(record_rdata) => record_rdata.rdlen(compress), + ParsedRData::Txt(record_rdata) => record_rdata.rdlen(compress), + } + } + + fn compose_rdata( + &self, + target: &mut Target, + ) -> Result<(), Target::AppendError> { + match self { + ParsedRData::A(record_rdata) => record_rdata.compose_rdata(target), + ParsedRData::Aaaa(record_rdata) => record_rdata.compose_rdata(target), + ParsedRData::Cname(record_rdata) => record_rdata.compose_rdata(target), + ParsedRData::Mx(record_rdata) => record_rdata.compose_rdata(target), + ParsedRData::Ns(record_rdata) => record_rdata.compose_rdata(target), + ParsedRData::Ptr(record_rdata) => record_rdata.compose_rdata(target), + ParsedRData::Soa(record_rdata) => record_rdata.compose_rdata(target), + ParsedRData::Srv(record_rdata) => record_rdata.compose_rdata(target), + ParsedRData::Txt(record_rdata) => record_rdata.compose_rdata(target), + } + } + + fn compose_canonical_rdata( + &self, + target: &mut Target, + ) -> Result<(), Target::AppendError> { + match self { + ParsedRData::A(record_rdata) => record_rdata.compose_canonical_rdata(target), + ParsedRData::Aaaa(record_rdata) => record_rdata.compose_canonical_rdata(target), + ParsedRData::Cname(record_rdata) => record_rdata.compose_canonical_rdata(target), + ParsedRData::Mx(record_rdata) => record_rdata.compose_canonical_rdata(target), + ParsedRData::Ns(record_rdata) => record_rdata.compose_canonical_rdata(target), + ParsedRData::Ptr(record_rdata) => record_rdata.compose_canonical_rdata(target), + ParsedRData::Soa(record_rdata) => record_rdata.compose_canonical_rdata(target), + ParsedRData::Srv(record_rdata) => record_rdata.compose_canonical_rdata(target), + ParsedRData::Txt(record_rdata) => record_rdata.compose_canonical_rdata(target), + } + } +} diff --git a/src/proto/mod.rs b/src/proto/mod.rs new file mode 100644 index 0000000..4670a6a --- /dev/null +++ b/src/proto/mod.rs @@ -0,0 +1 @@ +pub mod dns; diff --git a/src/resources/dns/external/mod.rs b/src/resources/dns/external/mod.rs new file mode 100644 index 0000000..f567899 --- /dev/null +++ b/src/resources/dns/external/mod.rs @@ -0,0 +1,2 @@ +pub mod rdata; +pub use rdata::*; diff --git a/src/resources/dns/external/rdata.rs b/src/resources/dns/external/rdata.rs new file mode 100644 index 0000000..ec00eb1 --- /dev/null +++ b/src/resources/dns/external/rdata.rs @@ -0,0 +1,437 @@ +use std::fmt::Write; +use std::net::{Ipv4Addr, Ipv6Addr}; + +use domain::base::{Rtype, scan::Symbol}; +use serde::{Deserialize, Serialize}; + +use crate::errors::Error; +use crate::validation; + +use crate::macros::{append_errors, push_error}; +use crate::resources::record::RecordParseError; +use crate::resources::dns::internal; + +/// Type used to serialize / deserialize resource records data to response / request +/// +#[derive(Debug, Deserialize, Serialize)] +#[serde(tag = "type", content = "rdata")] +#[serde(rename_all = "UPPERCASE")] +pub enum RData { + A(A), + Aaaa(Aaaa), + // TODO: CAA + Cname(Cname), + // TODO: DS + Mx(Mx), + Ns(Ns), + Ptr(Ptr), + Soa(Soa), + Srv(Srv), + // TODO: SSHFP + // TODO: SVCB / HTTPS + // TODO: TLSA + Txt(Txt), +} + +impl RData { + pub fn rtype(&self) -> Rtype { + match self { + RData::A(_) => Rtype::A, + RData::Aaaa(_) => Rtype::AAAA, + RData::Cname(_) => Rtype::CNAME, + RData::Mx(_) => Rtype::MX, + RData::Ns(_) => Rtype::NS, + RData::Ptr(_) => Rtype::PTR, + RData::Soa(_) => Rtype::SOA, + RData::Srv(_) => Rtype::SRV, + RData::Txt(_) => Rtype::TXT, + } + } + + pub fn validate(self) -> Result> { + let rdata = match self { + RData::A(data) => internal::RData::A(data.validate()?), + RData::Aaaa(data) => internal::RData::Aaaa(data.validate()?), + RData::Cname(data) => internal::RData::Cname(data.validate()?), + RData::Mx(data) => internal::RData::Mx(data.validate()?), + RData::Ns(data) => internal::RData::Ns(data.validate()?), + RData::Ptr(data) => internal::RData::Ptr(data.validate()?), + RData::Soa(data) => internal::RData::Soa(data.validate()?), + RData::Srv(data) => internal::RData::Srv(data.validate()?), + RData::Txt(data) => internal::RData::Txt(data.validate()?), + }; + + Ok(rdata) + } +} + +impl From for RData { + fn from(value: internal::RData) -> Self { + match value { + internal::RData::A(data) => RData::A(data.into()), + internal::RData::Aaaa(data) => RData::Aaaa(data.into()), + internal::RData::Cname(data) => RData::Cname(data.into()), + internal::RData::Mx(data) => RData::Mx(data.into()), + internal::RData::Ns(data) => RData::Ns(data.into()), + internal::RData::Ptr(data) => RData::Ptr(data.into()), + internal::RData::Soa(data) => RData::Soa(data.into()), + internal::RData::Srv(data) => RData::Srv(data.into()), + internal::RData::Txt(data) => RData::Txt(data.into()), + } + } +} + +/* --------- A --------- */ + +#[derive(Debug, Deserialize, Serialize)] +pub struct A { + pub address: String, +} + +impl From for A { + fn from(value: internal::A) -> Self { + A { + address: value.address.to_string(), + } + } +} + +impl A { + pub fn validate(self) -> Result> { + let mut errors = Vec::new(); + + let address = push_error!(self.address.parse::().map_err(|e| { + Error::from(RecordParseError::Ip4Address { input: self.address }) + .with_cause(&e.to_string()) + .with_path("/address") + }), errors); + + if errors.is_empty() { + Ok(internal::A { + address: address.unwrap() + }) + } else { + Err(errors) + } + } +} + +/* --------- AAAA --------- */ + +#[derive(Debug, Deserialize, Serialize)] +pub struct Aaaa { + pub address: String, +} + +impl From for Aaaa { + fn from(value: internal::Aaaa) -> Self { + Aaaa { + address: value.address.to_string(), + } + } +} + +impl Aaaa { + pub fn validate(self) -> Result> { + let mut errors = Vec::new(); + + // TODO: replace with custom validation + let address = push_error!(self.address.parse::().map_err(|e| { + Error::from(RecordParseError::Ip6Address { input: self.address }) + .with_cause(&e.to_string()) + .with_path("/address") + }), errors); + + if errors.is_empty() { + Ok(internal::Aaaa { + address: address.unwrap() + }) + } else { + Err(errors) + } + } +} + +/* --------- CNAME --------- */ + +#[derive(Debug, Deserialize, Serialize)] +pub struct Cname { + pub target: String, +} + +impl From for Cname { + fn from(value: internal::Cname) -> Self { + Cname { + target: value.target.to_string(), + } + } +} + +impl Cname { + pub fn validate(self) -> Result> { + let mut errors = Vec::new(); + + let cname = push_error!( + validation::normalize_domain(&self.target), + errors, "/target" + ); + + if errors.is_empty() { + Ok(internal::Cname { + target: internal::Name::new(cname.unwrap()) + }) + } else { + Err(errors) + } + } +} + +/* --------- MX --------- */ + +#[derive(Debug, Deserialize, Serialize)] +pub struct Mx { + // TODO: Validate number + pub preference: u16, + pub mail_exchanger: String, +} + +impl From for Mx { + fn from(value: internal::Mx) -> Self { + Mx { + preference: value.preference, + mail_exchanger: value.mail_exchanger.to_string(), + } + } +} + +impl Mx { + pub fn validate(self) -> Result> { + let mut errors = Vec::new(); + + let mail_exchanger = push_error!( + validation::normalize_domain(&self.mail_exchanger), + errors, "/mail_exchanger" + ); + + if errors.is_empty() { + Ok(internal::Mx { + preference: self.preference, + mail_exchanger: internal::Name::new(mail_exchanger.unwrap()), + }) + } else { + Err(errors) + } + } +} + +/* --------- NS --------- */ + +#[derive(Debug, Deserialize, Serialize)] +pub struct Ns { + pub target: String, +} + +impl From for Ns { + fn from(value: internal::Ns) -> Self { + Ns { + target: value.target.to_string(), + } + } +} + +impl Ns { + pub fn validate(self) -> Result> { + let mut errors = Vec::new(); + + let target = push_error!( + validation::normalize_domain(&self.target), + errors, "/target" + ); + + if errors.is_empty() { + Ok(internal::Ns { + target: internal::Name::new(target.unwrap()), + }) + } else { + Err(errors) + } + } +} + +/* --------- PTR --------- */ + +#[derive(Debug, Deserialize, Serialize)] +pub struct Ptr { + pub target: String, +} + +impl From for Ptr { + fn from(value: internal::Ptr) -> Self { + Ptr { + target: value.target.to_string(), + } + } +} + +impl Ptr { + pub fn validate(self) -> Result> { + let mut errors = Vec::new(); + + let target = push_error!( + validation::normalize_domain(&self.target), + errors, "/target" + ); + + if errors.is_empty() { + Ok(internal::Ptr { + target: internal::Name::new(target.unwrap()), + }) + } else { + Err(errors) + } + } + +} + +/* --------- SOA --------- */ + +#[derive(Debug, Deserialize, Serialize)] +pub struct Soa { + pub primary_server: String, + pub maintainer: String, + pub refresh: u32, + pub retry: u32, + pub expire: u32, + pub minimum: u32, + pub serial: u32, +} + +impl From for Soa { + fn from(value: internal::Soa) -> Self { + Soa { + primary_server: value.primary_server.to_string(), + maintainer: value.maintainer.to_string(), + refresh: value.refresh, + retry: value.retry, + expire: value.expire, + minimum: value.minimum, + serial: value.serial, + } + } +} + +impl Soa { + pub fn validate(self) -> Result> { + let mut errors = Vec::new(); + + let primary_server = push_error!( + validation::normalize_domain(&self.primary_server), + errors, "/primary_server" + ); + + let maintainer = push_error!( + validation::normalize_domain(&self.maintainer), + errors, "/maintainer" + ); + + if errors.is_empty() { + Ok(internal::Soa { + primary_server: internal::Name::new(primary_server.unwrap()), + maintainer: internal::Name::new(maintainer.unwrap()), + refresh: self.refresh, + retry: self.retry, + expire: self.expire, + minimum: self.minimum, + serial: self.serial, + }) + } else { + Err(errors) + } + } +} + +/* --------- SRV --------- */ + +#[derive(Debug, Deserialize, Serialize)] +pub struct Srv { + pub server: String, + pub port: u16, + pub priority: u16, + pub weight: u16, +} + +impl From for Srv { + fn from(value: internal::Srv) -> Self { + Srv { + server: value.server.to_string(), + port: value.port, + priority: value.priority, + weight: value.weight, + } + } +} + +impl Srv { + pub fn validate(self) -> Result> { + let mut errors = Vec::new(); + + let server = push_error!( + validation::normalize_domain(&self.server), + errors, "/server" + ); + + if errors.is_empty() { + Ok(internal::Srv { + server: internal::Name::new(server.unwrap()), + priority: self.priority, + weight: self.weight, + port: self.port, + }) + } else { + Err(errors) + } + } +} + + +/* --------- TXT --------- */ + +#[derive(Debug, Deserialize, Serialize)] +pub struct Txt { + pub text: String, +} + +impl From for Txt { + fn from(value: internal::Txt) -> Self { + + let mut concatenated_text = String::new(); + for c in value.text.iter() { + // Escapes '\' and non printable chars + let c = Symbol::display_from_octet(*c); + write!(concatenated_text, "{}", c).unwrap(); + } + + Txt { + text: concatenated_text + } + } +} + +impl Txt { + pub fn validate(self) -> Result> { + let mut errors = Vec::new(); + + let text = append_errors!( + validation::parse_txt_data(&self.text), + errors, "/text" + ); + + if errors.is_empty() { + Ok(internal::Txt { + text: text.unwrap() + }) + } else { + Err(errors) + } + } +} diff --git a/src/resources/dns/internal/mod.rs b/src/resources/dns/internal/mod.rs new file mode 100644 index 0000000..3d9a147 --- /dev/null +++ b/src/resources/dns/internal/mod.rs @@ -0,0 +1,3 @@ +pub mod rdata; + +pub use rdata::*; diff --git a/src/resources/dns/internal/rdata.rs b/src/resources/dns/internal/rdata.rs new file mode 100644 index 0000000..552575e --- /dev/null +++ b/src/resources/dns/internal/rdata.rs @@ -0,0 +1,78 @@ +use std::net::{Ipv4Addr, Ipv6Addr}; +use std::fmt; + +pub enum RData { + A(A), + Aaaa(Aaaa), + Cname(Cname), + Mx(Mx), + Ns(Ns), + Ptr(Ptr), + Soa(Soa), + Srv(Srv), + Txt(Txt), +} + +pub struct Name { + name: String +} + +impl Name { + pub fn new(name: String) -> Self { + Name { + name, + } + } +} + +impl fmt::Display for Name { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.name) + } +} + +pub struct A { + pub address: Ipv4Addr, +} + +pub struct Aaaa { + pub address: Ipv6Addr, +} + +pub struct Cname { + pub target: Name, +} + +pub struct Mx { + pub preference: u16, + pub mail_exchanger: Name, +} + +pub struct Ns { + pub target: Name, +} + +pub struct Ptr { + pub target: Name, +} + +pub struct Soa { + pub primary_server: Name, + pub maintainer: Name, + pub refresh: u32, + pub retry: u32, + pub expire: u32, + pub minimum: u32, + pub serial: u32, +} + +pub struct Srv { + pub server: Name, + pub port: u16, + pub priority: u16, + pub weight: u16, +} + +pub struct Txt { + pub text: Vec, +} diff --git a/src/resources/dns/mod.rs b/src/resources/dns/mod.rs new file mode 100644 index 0000000..11b23ca --- /dev/null +++ b/src/resources/dns/mod.rs @@ -0,0 +1,3 @@ +pub mod external; +pub mod internal; +//pub mod friendly; diff --git a/src/ressouces/mod.rs b/src/resources/mod.rs similarity index 95% rename from src/ressouces/mod.rs rename to src/resources/mod.rs index ae3c9f4..fb7d88e 100644 --- a/src/ressouces/mod.rs +++ b/src/resources/mod.rs @@ -20,5 +20,6 @@ pub use zone::{Zone, AddZoneMemberRequest, CreateZoneRequest}; */ pub mod zone; -pub mod rdata; +//pub mod rdata; pub mod record; +pub mod dns; diff --git a/src/ressouces/record.rs b/src/resources/record/mod.rs similarity index 83% rename from src/ressouces/record.rs rename to src/resources/record/mod.rs index aed47b6..9b29821 100644 --- a/src/ressouces/record.rs +++ b/src/resources/record/mod.rs @@ -4,7 +4,10 @@ use domain::base::{iana::Class, Name, Record as DnsRecord, Ttl}; use crate::{errors::Error, validation}; use crate::macros::{append_errors, push_error}; -use super::rdata::{ParsedRData, RData}; + +use crate::resources::dns::external; +use crate::resources::dns::internal; +use crate::proto; pub enum RecordParseError { Ip4Address { input: String }, @@ -20,7 +23,7 @@ pub enum RecordError { pub(crate) type DnsRecordImpl = DnsRecord< Name>, - ParsedRData>,Vec> + proto::dns::ParsedRData>,Vec> >; #[derive(Debug, Deserialize, Serialize)] @@ -28,15 +31,16 @@ pub struct Record { pub name: String, pub ttl: u32, #[serde(flatten)] - pub rdata: RData + pub rdata: external::RData } -impl> From>> for Record { - fn from(value: DnsRecord>) -> Self { +// TODO: Proto +impl> From>> for Record { + fn from(value: DnsRecord>) -> Self { Record { name: value.owner().to_string(), ttl: value.ttl().as_secs(), - rdata: value.into_data().into(), + rdata: internal::RData::from(value.into_data()).into(), } } } @@ -66,10 +70,13 @@ impl Record { }); let ttl = Ttl::from_secs(self.ttl); - let rdata = append_errors!(ParsedRData::try_from(self.rdata), errors, "/rdata"); + let rdata = append_errors!(self.rdata.validate(), errors, "/rdata"); + if errors.is_empty() { - Ok(DnsRecord::new(name.unwrap(), Class::IN, ttl, rdata.unwrap())) + // TODO: Split this in proto / external + let rdata = proto::dns::ParsedRData::try_from(rdata.unwrap()).unwrap(); + Ok(DnsRecord::new(name.unwrap(), Class::IN, ttl, rdata)) } else { Err(errors) } diff --git a/src/ressouces/session.rs b/src/resources/session.rs similarity index 100% rename from src/ressouces/session.rs rename to src/resources/session.rs diff --git a/src/ressouces/user.rs b/src/resources/user.rs similarity index 100% rename from src/ressouces/user.rs rename to src/resources/user.rs diff --git a/src/ressouces/zone.rs b/src/resources/zone.rs similarity index 99% rename from src/ressouces/zone.rs rename to src/resources/zone.rs index 4be11f5..ced2977 100644 --- a/src/ressouces/zone.rs +++ b/src/resources/zone.rs @@ -7,7 +7,7 @@ use crate::database::{BoxedDb, sqlite::SqliteDB}; use crate::dns::{BoxedZoneDriver, BoxedRecordDriver, DnsDriverError}; use crate::errors::Error; use crate::macros::push_error; -use crate::ressouces::record::RecordList; +use crate::resources::record::RecordList; use crate::validation; pub enum ZoneError { diff --git a/src/ressouces/rdata.rs b/src/ressouces/rdata.rs deleted file mode 100644 index afd8b62..0000000 --- a/src/ressouces/rdata.rs +++ /dev/null @@ -1,544 +0,0 @@ -use std::fmt::Write; -use std::net::{Ipv4Addr, Ipv6Addr}; - -use domain::base::rdata::ComposeRecordData; -use domain::base::scan::Symbol; -use domain::base::wire::{Composer, ParseError}; -use domain::base::{Name, ParseRecordData, ParsedName, RecordData, Rtype, ToName, Ttl}; -use domain::rdata; -use domain::dep::octseq::{Parser, Octets}; -use serde::{Deserialize, Serialize}; - -use crate::errors::Error; -use crate::validation; - -use crate::macros::{append_errors, push_error}; -use super::record::RecordParseError; - -/// Type used to serialize / deserialize resource records data to response / request -/// -#[derive(Debug, Deserialize, Serialize)] -#[serde(tag = "type", content = "rdata")] -#[serde(rename_all = "UPPERCASE")] -pub enum RData { - A(A), - Aaaa(Aaaa), - // TODO: CAA - Cname(Cname), - // TODO: DS - Mx(Mx), - Ns(Ns), - Ptr(Ptr), - Soa(Soa), - Srv(Srv), - // TODO: SSHFP - // TODO: SVCB / HTTPS - // TODO: TLSA - Txt(Txt), -} - -impl RData { - pub fn rtype(&self) -> Rtype { - match self { - RData::A(_) => Rtype::A, - RData::Aaaa(_) => Rtype::AAAA, - RData::Cname(_) => Rtype::CNAME, - RData::Mx(_) => Rtype::MX, - RData::Ns(_) => Rtype::NS, - RData::Ptr(_) => Rtype::PTR, - RData::Soa(_) => Rtype::SOA, - RData::Srv(_) => Rtype::SRV, - RData::Txt(_) => Rtype::TXT, - } - } -} - -pub enum ParsedRData { - A(rdata::A), - Aaaa(rdata::Aaaa), - Cname(rdata::Cname), - Mx(rdata::Mx), - Ns(rdata::Ns), - Ptr(rdata::Ptr), - Soa(rdata::Soa), - Srv(rdata::Srv), - Txt(rdata::Txt), -} - -impl> From> for RData { - fn from(value: ParsedRData) -> Self { - match value { - ParsedRData::A(record_rdata) => RData::A(record_rdata.into()), - ParsedRData::Aaaa(record_rdata) => RData::Aaaa(record_rdata.into()), - ParsedRData::Cname(record_rdata) => RData::Cname(record_rdata.into()), - ParsedRData::Mx(record_rdata) => RData::Mx(record_rdata.into()), - ParsedRData::Ns(record_rdata) => RData::Ns(record_rdata.into()), - ParsedRData::Ptr(record_rdata) => RData::Ptr(record_rdata.into()), - ParsedRData::Soa(record_rdata) => RData::Soa(record_rdata.into()), - ParsedRData::Srv(record_rdata) => RData::Srv(record_rdata.into()), - ParsedRData::Txt(record_rdata) => RData::Txt(record_rdata.into()), - } - } -} - -impl TryFrom for ParsedRData>, Vec> { - type Error = Vec; - - fn try_from(value: RData) -> Result { - let rdata = match value { - RData::A(record_rdata) => ParsedRData::A(record_rdata.parse_record()?), - RData::Aaaa(record_rdata) => ParsedRData::Aaaa(record_rdata.parse_record()?), - RData::Cname(record_rdata) => ParsedRData::Cname(record_rdata.parse_record()?), - RData::Mx(record_rdata) => ParsedRData::Mx(record_rdata.parse_record()?), - RData::Ns(record_rdata) => ParsedRData::Ns(record_rdata.parse_record()?), - RData::Ptr(record_rdata) => ParsedRData::Ptr(record_rdata.parse_record()?), - RData::Soa(record_rdata) => ParsedRData::Soa(record_rdata.parse_record()?), - RData::Srv(record_rdata) => ParsedRData::Srv(record_rdata.parse_record()?), - RData::Txt(record_rdata) => ParsedRData::Txt(record_rdata.parse_record()?), - }; - Ok(rdata) - } -} - - - -macro_rules! parse_name { - ($value:expr, $field:ident, $rtype:literal, $errors:expr) => { - { - let name = push_error!( - validation::normalize_domain(&$value.$field), - $errors, concat!("/", stringify!($field)) - ); - - let name = name.and_then(|name| { - push_error!( - name.parse::>().map_err(|e| { - Error::from(RecordParseError::RDataUnknown { - input: $value.$field, - field: stringify!(field).to_string(), - rtype: $rtype.to_string(), - }).with_cause(&e.to_string()) - }), - $errors, concat!("/", stringify!($field)) - ) - }); - - name - } - }; -} - -/* --------- A --------- */ - -#[derive(Debug, Deserialize, Serialize)] -pub struct A { - pub address: String, -} - -impl From for A { - fn from(record_data: rdata::A) -> Self { - A { address: record_data.addr().to_string() } - } -} - -impl A { - pub fn parse_record(self) -> Result> { - let mut errors = Vec::new(); - - let address = push_error!(self.address.parse::().map_err(|e| { - Error::from(RecordParseError::Ip4Address { input: self.address }) - .with_cause(&e.to_string()) - .with_path("/address") - }), errors); - - if errors.is_empty() { - Ok(rdata::A::new(address.unwrap())) - } else { - Err(errors) - } - } -} - -/* --------- AAAA --------- */ - -#[derive(Debug, Deserialize, Serialize)] -pub struct Aaaa { - pub address: String, -} - -impl From for Aaaa { - fn from(record_data: rdata::Aaaa) -> Self { - Aaaa { address: record_data.addr().to_string() } - } -} - -impl Aaaa { - pub fn parse_record(self) -> Result> { - let mut errors = Vec::new(); - - let address = push_error!(self.address.parse::().map_err(|e| { - Error::from(RecordParseError::Ip6Address { input: self.address }) - .with_cause(&e.to_string()) - .with_path("/address") - }), errors); - - if errors.is_empty() { - Ok(rdata::Aaaa::new(address.unwrap())) - } else { - Err(errors) - } - } -} - -/* --------- CNAME --------- */ - -#[derive(Debug, Deserialize, Serialize)] -pub struct Cname { - pub target: String, -} - -impl From> for Cname { - fn from(record_data: rdata::Cname) -> Self { - Cname { target: record_data.cname().to_string() } - } -} - -impl Cname { - pub fn parse_record(self) -> Result>>, Vec> { - let mut errors = Vec::new(); - - let cname = parse_name!(self, target, "CNAME", errors); - - if errors.is_empty() { - Ok(rdata::Cname::new(cname.unwrap())) - } else { - Err(errors) - } - - } -} - -/* --------- MX --------- */ - -#[derive(Debug, Deserialize, Serialize)] -pub struct Mx { - pub preference: u16, - pub mail_exchanger: String, -} - -impl From> for Mx { - fn from(record_data: rdata::Mx) -> Self { - Mx { - preference: record_data.preference(), - mail_exchanger: record_data.exchange().to_string() - } - } -} - -impl Mx { - fn parse_record(self) -> Result>>, Vec> { - let mut errors = Vec::new(); - - let mail_exchanger = parse_name!(self, mail_exchanger, "MX", errors); - - if errors.is_empty() { - Ok(rdata::Mx::new(self.preference, mail_exchanger.unwrap())) - } else { - Err(errors) - } - } -} - -/* --------- NS --------- */ - -#[derive(Debug, Deserialize, Serialize)] -pub struct Ns { - pub target: String, -} - -impl From> for Ns { - fn from(record_rdata: rdata::Ns) -> Self { - Ns { - target: record_rdata.nsdname().to_string(), - } - } -} - -impl Ns { - fn parse_record(self) -> Result>>, Vec> { - let mut errors = Vec::new(); - - let ns_name = parse_name!(self, target, "NS", errors); - - if errors.is_empty() { - Ok(rdata::Ns::new(ns_name.unwrap())) - } else { - Err(errors) - } - } -} - -/* --------- PTR --------- */ - -#[derive(Debug, Deserialize, Serialize)] -pub struct Ptr { - pub target: String, -} - -impl From> for Ptr { - fn from(record_rdata: rdata::Ptr) -> Self { - Ptr { - target: record_rdata.ptrdname().to_string(), - } - } -} - -impl Ptr { - fn parse_record(self) -> Result>>, Vec> { - let mut errors = Vec::new(); - - let ptr_name = parse_name!(self, target, "PTR", errors); - - if errors.is_empty() { - Ok(rdata::Ptr::new(ptr_name.unwrap())) - } else { - Err(errors) - } - } -} - -/* --------- SOA --------- */ - -#[derive(Debug, Deserialize, Serialize)] -pub struct Soa { - pub primary_server: String, - pub maintainer: String, - pub refresh: u32, - pub retry: u32, - pub expire: u32, - pub minimum: u32, - pub serial: u32, -} - -impl From> for Soa { - fn from(record_rdata: rdata::Soa) -> Self { - Soa { - primary_server: record_rdata.mname().to_string(), - maintainer: record_rdata.rname().to_string(), - refresh: record_rdata.refresh().as_secs(), - retry: record_rdata.retry().as_secs(), - expire: record_rdata.expire().as_secs(), - minimum: record_rdata.minimum().as_secs(), - serial: record_rdata.serial().into(), - } - } -} - -impl Soa { - fn parse_record(self) -> Result>>, Vec> { - let mut errors = Vec::new(); - - let primary_ns = parse_name!(self, primary_server, "SOA", errors); - let maintainer = parse_name!(self, maintainer, "SOA", errors); - - if errors.is_empty() { - Ok(rdata::Soa::new( - primary_ns.unwrap(), - maintainer.unwrap(), - self.refresh.into(), - Ttl::from_secs(self.retry), - Ttl::from_secs(self.expire), - Ttl::from_secs(self.minimum), - Ttl::from_secs(self.serial), - )) - } else { - Err(errors) - } - } -} - -/* --------- SRV --------- */ - -#[derive(Debug, Deserialize, Serialize)] -pub struct Srv { - pub server: String, - pub port: u16, - pub priority: u16, - pub weight: u16, -} - -impl From> for Srv { - fn from(record_data: rdata::Srv) -> Self { - Srv { - server: record_data.target().to_string(), - priority: record_data.priority(), - weight: record_data.weight(), - port: record_data.port(), - } - } -} - -impl Srv { - fn parse_record(self) -> Result>>, Vec> { - let mut errors = Vec::new(); - - let server = parse_name!(self, server, "SRV", errors); - - if errors.is_empty() { - Ok(rdata::Srv::new( - self.priority, - self.weight, - self.port, - server.unwrap(), - )) - } else { - Err(errors) - } - } -} - - -/* --------- TXT --------- */ - -#[derive(Debug, Deserialize, Serialize)] -pub struct Txt { - pub text: String, -} - -impl> From> for Txt { - fn from(record_data: rdata::Txt) -> Self { - let mut concatenated_text = String::new(); - for text in record_data.iter() { - for c in text { - // Escapes '\' and non printable chars - let c = Symbol::display_from_octet(*c); - write!(concatenated_text, "{}", c).unwrap(); - } - } - - Txt { - text: concatenated_text - } - } -} - -impl Txt { - fn parse_record(self) -> Result>, Vec> { - let mut errors = Vec::new(); - let data = append_errors!(validation::parse_txt_data(&self.text), errors, "/text"); - let data = data.and_then(|data| { - push_error!(rdata::Txt::build_from_slice(&data).map_err(|e| { - Error::from(RecordParseError::RDataUnknown { - input: self.text, - field: "text".into(), - rtype: "TXT".into(), - }).with_cause(&e.to_string()) - .with_path("/text") - }), errors) - }); - - - - if errors.is_empty() { - Ok(data.unwrap()) - } else { - Err(errors) - } - } -} - -/* --------- ParsedRData: domain traits impl --------- */ - -impl ParsedRData { - pub fn rtype(&self) -> Rtype { - match self { - ParsedRData::A(_) => Rtype::A, - ParsedRData::Aaaa(_) => Rtype::AAAA, - ParsedRData::Cname(_) => Rtype::CNAME, - ParsedRData::Mx(_) => Rtype::MX, - ParsedRData::Ns(_) => Rtype::NS, - ParsedRData::Ptr(_) => Rtype::PTR, - ParsedRData::Soa(_) => Rtype::SOA, - ParsedRData::Srv(_) => Rtype::SRV, - ParsedRData::Txt(_) => Rtype::TXT, - } - } -} - -impl RecordData for ParsedRData { - fn rtype(&self) -> Rtype { - ParsedRData::rtype(self) - } -} - -impl<'a, Octs: Octets + ?Sized> ParseRecordData<'a, Octs> for ParsedRData>, Octs::Range<'a>> { - fn parse_rdata( - rtype: Rtype, - parser: &mut Parser<'a, Octs>, - ) -> Result, ParseError> { - let record = match rtype { - Rtype::A => ParsedRData::A(rdata::A::parse(parser)?), - Rtype::AAAA => ParsedRData::Aaaa(rdata::Aaaa::parse(parser)?), - Rtype::CNAME => ParsedRData::Cname(rdata::Cname::parse(parser)?), - Rtype::MX => ParsedRData::Mx(rdata::Mx::parse(parser)?), - Rtype::NS => ParsedRData::Ns(rdata::Ns::parse(parser)?), - Rtype::PTR => ParsedRData::Ptr(rdata::Ptr::parse(parser)?), - Rtype::SOA => ParsedRData::Soa(rdata::Soa::parse(parser)?), - Rtype::SRV => ParsedRData::Srv(rdata::Srv::parse(parser)?), - Rtype::TXT => ParsedRData::Txt(rdata::Txt::parse(parser)?), - _ => return Ok(None) - }; - - Ok(Some(record)) - } -} - -impl> ComposeRecordData for ParsedRData { - fn rdlen(&self, compress: bool) -> Option { - match self { - ParsedRData::A(record_rdata) => record_rdata.rdlen(compress), - ParsedRData::Aaaa(record_rdata) => record_rdata.rdlen(compress), - ParsedRData::Cname(record_rdata) => record_rdata.rdlen(compress), - ParsedRData::Mx(record_rdata) => record_rdata.rdlen(compress), - ParsedRData::Ns(record_rdata) => record_rdata.rdlen(compress), - ParsedRData::Ptr(record_rdata) => record_rdata.rdlen(compress), - ParsedRData::Soa(record_rdata) => record_rdata.rdlen(compress), - ParsedRData::Srv(record_rdata) => record_rdata.rdlen(compress), - ParsedRData::Txt(record_rdata) => record_rdata.rdlen(compress), - } - } - - fn compose_rdata( - &self, - target: &mut Target, - ) -> Result<(), Target::AppendError> { - match self { - ParsedRData::A(record_rdata) => record_rdata.compose_rdata(target), - ParsedRData::Aaaa(record_rdata) => record_rdata.compose_rdata(target), - ParsedRData::Cname(record_rdata) => record_rdata.compose_rdata(target), - ParsedRData::Mx(record_rdata) => record_rdata.compose_rdata(target), - ParsedRData::Ns(record_rdata) => record_rdata.compose_rdata(target), - ParsedRData::Ptr(record_rdata) => record_rdata.compose_rdata(target), - ParsedRData::Soa(record_rdata) => record_rdata.compose_rdata(target), - ParsedRData::Srv(record_rdata) => record_rdata.compose_rdata(target), - ParsedRData::Txt(record_rdata) => record_rdata.compose_rdata(target), - } - } - - fn compose_canonical_rdata( - &self, - target: &mut Target, - ) -> Result<(), Target::AppendError> { - match self { - ParsedRData::A(record_rdata) => record_rdata.compose_canonical_rdata(target), - ParsedRData::Aaaa(record_rdata) => record_rdata.compose_canonical_rdata(target), - ParsedRData::Cname(record_rdata) => record_rdata.compose_canonical_rdata(target), - ParsedRData::Mx(record_rdata) => record_rdata.compose_canonical_rdata(target), - ParsedRData::Ns(record_rdata) => record_rdata.compose_canonical_rdata(target), - ParsedRData::Ptr(record_rdata) => record_rdata.compose_canonical_rdata(target), - ParsedRData::Soa(record_rdata) => record_rdata.compose_canonical_rdata(target), - ParsedRData::Srv(record_rdata) => record_rdata.compose_canonical_rdata(target), - ParsedRData::Txt(record_rdata) => record_rdata.compose_canonical_rdata(target), - } - } -} diff --git a/src/routes/api/zones.rs b/src/routes/api/zones.rs index 4f54e4b..5dc0739 100644 --- a/src/routes/api/zones.rs +++ b/src/routes/api/zones.rs @@ -3,8 +3,8 @@ use axum::Json; use crate::AppState; use crate::errors::Error; -use crate::ressouces::zone::{CreateZoneRequest, Zone}; -use crate::ressouces::record::{AddRecordsRequest, Record, RecordList}; +use crate::resources::zone::{CreateZoneRequest, Zone}; +use crate::resources::record::{AddRecordsRequest, Record, RecordList}; pub async fn create_zone( diff --git a/src/routes/ui/zones.rs b/src/routes/ui/zones.rs index e9d91cc..6f8dd0a 100644 --- a/src/routes/ui/zones.rs +++ b/src/routes/ui/zones.rs @@ -3,7 +3,7 @@ use serde_json::{Value, json}; use crate::AppState; use crate::errors::Error; -use crate::ressouces::zone::Zone; +use crate::resources::zone::Zone; use crate::template::Template;