write some tests for Show

This commit is contained in:
Terts Diepraam
2024-09-04 15:05:29 +02:00
parent d9eade978f
commit ac0fca87dd
4 changed files with 162 additions and 54 deletions
+159 -40
View File
@@ -1,18 +1,17 @@
//! Modelled roughly after `std::fmt::Display`
use core::fmt;
#[derive(Clone, Copy, Debug)]
pub struct Error;
impl From<std::fmt::Error> for Error {
fn from(value: std::fmt::Error) -> Self {
impl From<fmt::Error> for Error {
fn from(_: fmt::Error) -> Self {
Self
}
}
pub type Result = std::result::Result<(), Error>;
/// Show a value as zonefile format
pub trait Show {
fn show(&self, p: &mut Presenter<'_>) -> Result;
@@ -23,15 +22,15 @@ pub trait Show {
impl<T: Show + ?Sized> fmt::Display for ZoneFileDisplay<'_, T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.inner.show(&mut Presenter {
writer: &mut SimplePresentationWriter::new(f)
}).map_err(|_| fmt::Error)
self.inner
.show(&mut Presenter {
writer: &mut SimplePresentationWriter::new(f),
})
.map_err(|_| fmt::Error)
}
}
ZoneFileDisplay {
inner: self
}
ZoneFileDisplay { inner: self }
}
}
@@ -39,19 +38,41 @@ impl<T: Show> Show for &T {
fn show(&self, p: &mut Presenter<'_>) -> Result {
T::show(self, p)
}
}
}
/// Determines how a zonefile is formatted
pub trait PresentationWriter {
/// Push a token to the zonefile
fn fmt_token(&mut self, args: std::fmt::Arguments<'_>) -> Result;
/// Start a block of grouped tokens
///
/// This might push `'('` to the zonefile, but may be ignored by the
/// `PresentationWriter`.
fn begin_block(&mut self) -> Result;
/// End a block of grouped tokens
///
/// This might push `'('` to the zonefile, but may be ignored by the
/// `PresentationWriter`.
fn end_block(&mut self) -> Result;
/// Write a comment
///
/// This may be ignored.
fn fmt_comment(&mut self, args: std::fmt::Arguments<'_>) -> Result;
/// End the current record and start a new line
fn newline(&mut self) -> Result;
}
/// The simplest possible zonefile writer
///
/// This writer does not do any alignment, comments and squeezes each record
/// onto a single line.
struct SimplePresentationWriter<'a> {
first: bool,
writer: &'a mut (dyn std::fmt::Write + 'a),
writer: &'a mut (dyn fmt::Write + 'a),
}
impl<'a> SimplePresentationWriter<'a> {
@@ -64,7 +85,7 @@ impl<'a> SimplePresentationWriter<'a> {
}
impl PresentationWriter for SimplePresentationWriter<'_> {
fn fmt_token(&mut self, args: std::fmt::Arguments<'_>) -> Result {
fn fmt_token(&mut self, args: fmt::Arguments<'_>) -> Result {
if !self.first {
self.writer.write_char(' ')?;
}
@@ -81,22 +102,29 @@ impl PresentationWriter for SimplePresentationWriter<'_> {
Ok(())
}
fn fmt_comment(&mut self, _args: std::fmt::Arguments<'_>) -> Result {
fn fmt_comment(&mut self, _args: fmt::Arguments<'_>) -> Result {
Ok(())
}
fn newline(&mut self) -> Result {
self.writer.write_char('\n');
self.writer.write_char('\n')?;
self.first = true;
Ok(())
}
}
/// A more structured wrapper around a [`PresentationWriter`]
///
/// Writing comments is not allowed with this type because comments can only
/// appear when a token is surrounded by parentheses.
pub struct Presenter<'a> {
writer: &'a mut (dyn PresentationWriter + 'a),
}
impl<'a> Presenter<'a> {
/// Start a sequence of grouped tokens
///
/// The block might be surrounded by `(` and `)` in a multiline format.
pub fn block<'b>(&'b mut self) -> Block<'a, 'b> {
let result = self.writer.begin_block();
Block {
@@ -105,7 +133,8 @@ impl<'a> Presenter<'a> {
}
}
pub fn write_token(&mut self, token: impl std::fmt::Display) -> Result {
/// Push a token
pub fn write_token(&mut self, token: impl fmt::Display) -> Result {
self.writer.fmt_token(format_args!("{token}"))
}
}
@@ -117,49 +146,139 @@ pub struct Block<'a, 'b> {
}
impl<'a, 'b> Block<'a, 'b> {
pub fn write_token(&mut self, token: impl std::fmt::Display) -> &mut Self {
self.result = self
.result
.and_then(|_| self.presenter.write_token(token));
/// Push a token
pub fn write_token(
&mut self,
token: impl std::fmt::Display,
) -> &mut Self {
self.result =
self.result.and_then(|_| self.presenter.write_token(token));
self
}
/// Push the sequence of tokens generated by the [`Show`] implementation
/// of the `token`
pub fn write_show(&mut self, token: impl Show) -> &mut Self {
self.result = self
.result
.and_then(|_| token.show(self.presenter));
self.result = self.result.and_then(|_| token.show(self.presenter));
self
}
pub fn write_comment(&mut self, s: impl std::fmt::Display) -> &mut Self {
self.result = self
.result
.and_then(|_| self.presenter.writer.fmt_comment(format_args!("{s}")));
/// Write a comment
///
/// This may be ignored.
pub fn write_comment(&mut self, s: impl fmt::Display) -> &mut Self {
self.result = self.result.and_then(|_| {
self.presenter.writer.fmt_comment(format_args!("{s}"))
});
self
}
/// Finish the block.
///
/// This _must_ be called before the `Block` is dropped.
pub fn finish(&mut self) -> Result {
self.result.and_then(|_| self.presenter.writer.end_block())
}
}
#[cfg(test)]
#[cfg(all(test, feature = "std"))]
mod test {
use std::string::ToString as _;
use std::vec::Vec;
use crate::base::iana::{Class, DigestAlg, SecAlg};
use crate::base::show::Show;
use crate::base::{Record, Name, Ttl};
use crate::rdata::A;
use crate::base::iana::Class;
use crate::base::{Name, Record, Ttl};
use crate::rdata::{Cds, Cname, Ds, Mx, Txt, A};
fn create_record<Data>(data: Data) -> Record<&'static Name<[u8]>, Data> {
let name = Name::from_slice(b"\x07example\x03com\x00").unwrap();
Record::new(name, Class::IN, Ttl::from_secs(3600), data)
}
#[test]
fn a_record() {
let name = Name::from_slice(b"\x07example\x03com\x00").unwrap();
let record = Record::new(
name,
Class::IN,
Ttl::from_secs(3600),
A::new("128.140.76.106".parse().unwrap()),
let record = create_record(A::new("128.140.76.106".parse().unwrap()));
assert_eq!(
"example.com. 3600 IN A 128.140.76.106",
record.display_zonefile().to_string()
);
eprintln!("{}", record.display_zonefile());
panic!()
}
}
#[test]
fn cname_record() {
let record = create_record(Cname::new(
Name::from_slice(b"\x07example\x03com\x00").unwrap(),
));
assert_eq!(
"example.com. 3600 IN CNAME example.com.",
record.display_zonefile().to_string()
);
}
#[test]
fn ds_key_record() {
let record = create_record(
Ds::new(
5414,
SecAlg::ED25519,
DigestAlg::SHA256,
&[0xDE, 0xAD, 0xBE, 0xEF],
)
.unwrap(),
);
assert_eq!(
"example.com. 3600 IN DS 5414 15 2 DEADBEEF",
record.display_zonefile().to_string()
);
}
#[test]
fn cds_record() {
let record = create_record(
Cds::new(
5414,
SecAlg::ED25519,
DigestAlg::SHA256,
&[0xDE, 0xAD, 0xBE, 0xEF],
)
.unwrap(),
);
assert_eq!(
"example.com. 3600 IN CDS 5414 15 2 DEADBEEF",
record.display_zonefile().to_string()
);
}
#[test]
fn mx_record() {
let record = create_record(Mx::new(
20,
Name::from_slice(b"\x07example\x03com\x00").unwrap(),
));
assert_eq!(
"example.com. 3600 IN MX 20 example.com.",
record.display_zonefile().to_string()
);
}
#[test]
fn txt_record() {
let record = create_record(Txt::<Vec<u8>>::build_from_slice(
b"this is a string that is longer than 255 characters if I just \
type a little bit more to pad this test out and then write some \
more like a silly monkey with a typewriter accidentally writing \
some shakespeare along the way but it feels like I have to type \
even longer to hit that limit!\
").unwrap());
assert_eq!(
"example.com. 3600 IN TXT \
\"this is a string that is longer than 255 characters if I just \
type a little bit more to pad this test out and then write some \
more like a silly monkey with a typewriter accidentally writing \
some shakespeare along the way but it feels like I have to type \
e\" \"ven longer to hit that limit!\"",
record.display_zonefile().to_string()
);
}
}
+1 -12
View File
@@ -680,22 +680,11 @@ impl<Octs: AsRef<[u8]>> fmt::Debug for Cds<Octs> {
impl<Octs: AsRef<[u8]>> Show for Cds<Octs> {
fn show(&self, p: &mut Presenter) -> show::Result {
struct Data<'a>(&'a [u8]);
impl std::fmt::Display for Data<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for ch in self.0 {
write!(f, " {:02x}", *ch)?
}
Ok(())
}
}
p.block()
.write_token(self.key_tag)
.write_show(self.algorithm)
.write_show(self.digest_type)
.write_token(Data(self.digest.as_ref()))
.write_token(base16::encode_display(&self.digest))
.finish()
}
}
+1 -1
View File
@@ -234,7 +234,7 @@ impl<N: fmt::Display> Show for Mx<N> {
fn show(&self, p: &mut Presenter) -> show::Result {
p.block()
.write_token(self.preference)
.write_token(&self.exchange)
.write_token(format_args!("{}.", self.exchange))
.finish()
}
}
+1 -1
View File
@@ -448,7 +448,7 @@ impl<Octs: AsRef<[u8]>> fmt::Debug for Txt<Octs> {
//--- Show
impl<Octs: AsRef<[u8]>> Show for Txt<Octs> {
impl<Octs> Show for Txt<Octs> where Octs: AsRef<[u8]> {
fn show(&self, p: &mut Presenter) -> show::Result {
let mut block = p.block();
for slice in self.iter_charstrs() {