use std::slice::Iter;
use serde::{Deserialize, Serialize};
#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
#[allow(clippy::upper_case_acronyms)]
pub struct TXT {
txt_data: Box<[Box<[u8]>]>,
}
impl TXT {
pub fn new(txt_data: Vec<String>) -> TXT {
TXT {
txt_data: txt_data
.into_iter()
.map(|s| s.as_bytes().to_vec().into_boxed_slice())
.collect::<Vec<_>>()
.into_boxed_slice(),
}
}
pub fn txt_data(&self) -> &[Box<[u8]>] {
&self.txt_data
}
pub fn iter(&self) -> Iter<'_, Box<[u8]>> {
self.txt_data.iter()
}
pub fn is_spf(&self) -> bool {
if let Some(first) = self.iter().next() {
let str = String::from_utf8_lossy(first);
str.starts_with("v=spf")
} else {
false
}
}
pub fn as_string(&self) -> String {
self.iter()
.map(|x| String::from_utf8_lossy(x))
.collect::<Vec<_>>()
.join("")
}
}
#[doc(hidden)]
impl From<hickory_resolver::proto::rr::rdata::TXT> for TXT {
fn from(txt: hickory_resolver::proto::rr::rdata::TXT) -> Self {
let txt_data = txt.iter().cloned().collect::<Vec<_>>().into_boxed_slice();
TXT { txt_data }
}
}
#[cfg(test)]
mod tests {
use spectral::prelude::*;
use super::*;
#[test]
fn is_spf() {
crate::utils::tests::logging::init();
let record = "v=spf1 ip4:192.168.0.0/24 +ip6:fc00::/7 ?a a/24 a:offsite.example.com/24 ~mx mx/24 mx:mx.example.com/24 -ptr +ptr:mx.example.com exists:%{ir}.%{l1r+-}._spf.%{d} ?include:_spf.example.com redirect=_spf.example.com exp=explain._spf.%{d} -all";
let txt = TXT::new(vec![record.to_string()]);
asserting("txt record is SPF record").that(&txt.is_spf()).is_true();
}
#[test]
fn is_not_spf() {
crate::utils::tests::logging::init();
let record = "3897592857random_stuff09389025";
let txt = TXT::new(vec![record.to_string()]);
asserting("txt record is SPF record").that(&txt.is_spf()).is_false();
}
#[test]
fn as_string_single_chunk() {
let txt = TXT::new(vec!["hello world".to_string()]);
assert_eq!(txt.as_string(), "hello world");
}
#[test]
fn as_string_multi_chunk() {
let txt = TXT::new(vec!["chunk1".to_string(), " chunk2".to_string(), " chunk3".to_string()]);
assert_eq!(txt.as_string(), "chunk1 chunk2 chunk3");
}
#[test]
fn as_string_empty() {
let txt = TXT::new(vec![]);
assert_eq!(txt.as_string(), "");
}
#[test]
fn as_string_empty_chunks() {
let txt = TXT::new(vec!["".to_string(), "".to_string()]);
assert_eq!(txt.as_string(), "");
}
#[test]
fn as_string_single_empty_chunk() {
let txt = TXT::new(vec!["".to_string()]);
assert_eq!(txt.as_string(), "");
}
#[test]
fn is_spf_empty() {
let txt = TXT::new(vec![]);
assert!(!txt.is_spf());
}
#[test]
fn txt_data_round_trip() {
let txt = TXT::new(vec!["a".to_string(), "b".to_string()]);
assert_eq!(txt.txt_data().len(), 2);
assert_eq!(txt.txt_data()[0].as_ref(), b"a");
assert_eq!(txt.txt_data()[1].as_ref(), b"b");
}
}