use nom::error::Error as NomError;
use nom::Err;
use crate::{Error, Result};
#[derive(Debug, PartialEq)]
pub struct Bimi<'a> {
version: &'a str,
logo: Option<&'a str>,
authority: Option<&'a str>,
}
#[allow(clippy::should_implement_trait)]
impl<'a> Bimi<'a> {
pub fn from_str(txt: &'a str) -> Result<Bimi<'a>> {
match parser::bimi(txt) {
Ok((_, result)) => Ok(result),
Err(Err::Incomplete(_)) => Err(Error::ParserError {
what: txt.to_string(),
to: "BIMI TXT Record",
why: "input is incomplete".to_string(),
}),
Err(Err::Error(NomError { input: what, code: why }))
| Err(Err::Failure(NomError { input: what, code: why })) => Err(Error::ParserError {
what: what.to_string(),
to: "BIMI TXT Record",
why: format!("{:?}", why),
}),
}
}
pub fn version(&self) -> &str {
self.version
}
pub fn logo(&self) -> Option<&str> {
self.logo
}
pub fn authority(&self) -> Option<&str> {
self.authority
}
}
pub(crate) mod parser {
use super::Bimi;
use crate::resources::rdata::parsed_txt::tag_value;
use nom::error::{Error as NomError, ErrorKind};
use nom::{Err, IResult};
pub fn bimi(input: &str) -> IResult<&str, Bimi<'_>> {
let (rest, tags) = tag_value::parser::tag_list(input)?;
let version = tags.first().map(|(k, v)| (*k, *v));
match version {
Some(("v", "BIMI1")) => {}
_ => return Err(Err::Error(NomError::new(input, ErrorKind::Tag))),
}
let find = |key: &str| -> Option<&str> {
tags.iter()
.find(|(k, _)| *k == key)
.map(|(_, v)| *v)
.and_then(|v| if v.is_empty() { None } else { Some(v) })
};
Ok((
rest,
Bimi {
version: "BIMI1",
logo: find("l"),
authority: find("a"),
},
))
}
}
#[cfg(test)]
mod test {
use super::parser::bimi;
#[test]
fn both_tags() {
crate::utils::tests::logging::init();
let record = "v=BIMI1; l=https://example.com/logo.svg; a=https://example.com/cert.pem";
let (_, result) = bimi(record).expect("failed to parse BIMI record");
assert_eq!(result.version(), "BIMI1");
assert_eq!(result.logo(), Some("https://example.com/logo.svg"));
assert_eq!(result.authority(), Some("https://example.com/cert.pem"));
}
#[test]
fn logo_only() {
crate::utils::tests::logging::init();
let record = "v=BIMI1; l=https://example.com/brand.svg";
let (_, result) = bimi(record).expect("failed to parse BIMI record");
assert_eq!(result.logo(), Some("https://example.com/brand.svg"));
assert_eq!(result.authority(), None);
}
#[test]
fn empty_logo() {
crate::utils::tests::logging::init();
let record = "v=BIMI1; l=; a=https://example.com/cert.pem";
let (_, result) = bimi(record).expect("failed to parse BIMI record");
assert_eq!(result.logo(), None);
assert_eq!(result.authority(), Some("https://example.com/cert.pem"));
}
#[test]
fn invalid_version() {
crate::utils::tests::logging::init();
let record = "v=BIMI2; l=https://example.com/logo.svg";
let res = bimi(record);
assert!(res.is_err());
}
}