use super::*;
use nom::{
branch::alt,
bytes::complete::{tag, tag_no_case, take_while, take_while1, take_while_m_n},
character::complete::{alpha1, alphanumeric0, alphanumeric1, digit0, digit1, one_of},
combinator::{all_consuming, map, map_res, opt, recognize, value, verify},
error::ErrorKind,
multi::{many0, many1, many_m_n},
sequence::{delimited, pair, preceded, separated_pair, tuple},
IResult,
};
use std::{
net::{Ipv4Addr, Ipv6Addr},
num::NonZeroU8,
str::FromStr,
};
impl FromStr for Record {
type Err = ParseRecordError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match all_consuming(record)(s) {
Ok((_, result)) => Ok(result),
Err(_) => Err(ParseRecordError),
}
}
}
fn record(input: &str) -> IResult<&str, Record> {
map(
delimited(tag_no_case("v=spf1"), many0(term), take_while(is_space)),
|terms| Record { terms },
)(input)
}
fn term(input: &str) -> IResult<&str, Term> {
preceded(
take_while1(is_space),
alt((
map(directive, Term::Directive),
map(modifier, Term::Modifier),
)),
)(input)
}
fn directive(input: &str) -> IResult<&str, Directive> {
map(
pair(opt(qualifier), mechanism),
|(qualifier, mechanism)| Directive { qualifier, mechanism },
)(input)
}
fn qualifier(input: &str) -> IResult<&str, Qualifier> {
map(one_of("+-?~"), |c| match c {
'+' => Qualifier::Pass,
'-' => Qualifier::Fail,
'?' => Qualifier::Neutral,
'~' => Qualifier::Softfail,
_ => unreachable!(),
})(input)
}
fn mechanism(input: &str) -> IResult<&str, Mechanism> {
alt((
mechanism_all,
mechanism_include,
mechanism_a,
mechanism_mx,
mechanism_ptr,
mechanism_ip4,
mechanism_ip6,
mechanism_exists,
))(input)
}
fn mechanism_all(input: &str) -> IResult<&str, Mechanism> {
value(Mechanism::All, tag_no_case("all"))(input)
}
fn mechanism_include(input: &str) -> IResult<&str, Mechanism> {
map(
preceded(tag_no_case("include:"), domain_spec),
|domain_spec| Mechanism::Include(Include { domain_spec }),
)(input)
}
fn mechanism_a(input: &str) -> IResult<&str, Mechanism> {
map(
preceded(
tag_no_case("a"),
pair(opt(preceded(tag(":"), domain_spec)), opt(dual_cidr_length)),
),
|(domain_spec, prefix_len)| Mechanism::A(A { domain_spec, prefix_len }),
)(input)
}
fn mechanism_mx(input: &str) -> IResult<&str, Mechanism> {
map(
preceded(
tag_no_case("mx"),
pair(opt(preceded(tag(":"), domain_spec)), opt(dual_cidr_length)),
),
|(domain_spec, prefix_len)| Mechanism::Mx(Mx { domain_spec, prefix_len }),
)(input)
}
fn mechanism_ptr(input: &str) -> IResult<&str, Mechanism> {
map(
preceded(tag_no_case("ptr"), opt(preceded(tag(":"), domain_spec))),
|domain_spec| Mechanism::Ptr(Ptr { domain_spec }),
)(input)
}
fn mechanism_ip4(input: &str) -> IResult<&str, Mechanism> {
map(
preceded(tag_no_case("ip4:"), pair(ip4_network, opt(ip4_cidr_length))),
|(addr, prefix_len)| Mechanism::Ip4(Ip4 { addr, prefix_len }),
)(input)
}
fn mechanism_ip6(input: &str) -> IResult<&str, Mechanism> {
map(
preceded(tag_no_case("ip6:"), pair(ip6_network, opt(ip6_cidr_length))),
|(addr, prefix_len)| Mechanism::Ip6(Ip6 { addr, prefix_len }),
)(input)
}
fn mechanism_exists(input: &str) -> IResult<&str, Mechanism> {
map(
preceded(tag_no_case("exists:"), domain_spec),
|domain_spec| Mechanism::Exists(Exists { domain_spec }),
)(input)
}
fn modifier(input: &str) -> IResult<&str, Modifier> {
alt((
modifier_redirect,
modifier_explanation,
modifier_unknown,
))(input)
}
fn modifier_redirect(input: &str) -> IResult<&str, Modifier> {
map(
preceded(tag_no_case("redirect="), domain_spec),
|domain_spec| Modifier::Redirect(Redirect { domain_spec }),
)(input)
}
fn modifier_explanation(input: &str) -> IResult<&str, Modifier> {
map(
preceded(tag_no_case("exp="), domain_spec),
|domain_spec| Modifier::Explanation(Explanation { domain_spec }),
)(input)
}
fn modifier_unknown(input: &str) -> IResult<&str, Modifier> {
map(
separated_pair(
verify(name, is_unknown_modifier_name),
tag("="),
macro_string,
),
|(name, value)| Modifier::Unknown(Unknown { name, value }),
)(input)
}
fn name(input: &str) -> IResult<&str, Name> {
map(
recognize(pair(alpha1, take_while(is_name_char))),
|s| Name(String::from(s)),
)(input)
}
fn ip4_network(input: &str) -> IResult<&str, Ipv4Addr> {
map(
tuple((qnum, tag("."), qnum, tag("."), qnum, tag("."), qnum)),
|(a, _, b, _, c, _, d)| Ipv4Addr::new(a, b, c, d),
)(input)
}
fn qnum(input: &str) -> IResult<&str, u8> {
map_res(
alt((
recognize(pair(tag("25"), take_while_m_n(1, 1, is_digit_upto5))),
recognize(tuple((
tag("2"),
take_while_m_n(1, 1, is_digit_upto4),
take_while_m_n(1, 1, is_digit),
))),
recognize(pair(tag("1"), take_while_m_n(2, 2, is_digit))),
recognize(pair(
take_while_m_n(1, 1, is_positive_digit),
take_while_m_n(1, 1, is_digit),
)),
take_while_m_n(1, 1, is_digit),
)),
u8::from_str,
)(input)
}
fn is_digit_upto5(c: char) -> bool {
matches!(c, '0'..='5')
}
fn is_digit_upto4(c: char) -> bool {
matches!(c, '0'..='4')
}
fn ip6_network(mut input: &str) -> IResult<&str, Ipv6Addr> {
let (mut hi, mut lo) = (Vec::with_capacity(8), Vec::with_capacity(8));
if let Ok((i, piece)) = ip6_piece(input) {
input = i;
hi.push(piece);
if let Ok((i, pieces)) = many_m_n(1, 5, preceded(tag(":"), ip6_piece))(input) {
input = i;
hi.extend(pieces);
if hi.len() == 6 {
if let Ok((i, addr)) = preceded(tag(":"), ip4_network)(input) {
return Ok((i, ipv6_addr_from_pieces_ipv4(hi, lo, addr)));
}
if let Ok((i, pieces)) = many_m_n(1, 2, preceded(tag(":"), ip6_piece))(input) {
input = i;
hi.extend(pieces);
if hi.len() == 8 {
return Ok((input, ipv6_addr_from_pieces(hi, lo)));
}
}
}
}
}
let (mut input, _) = tag("::")(input)?;
if hi.len() <= 5 {
if let Ok((i, addr)) = ip4_network(input) {
return Ok((i, ipv6_addr_from_pieces_ipv4(hi, lo, addr)));
}
}
if hi.len() <= 6 {
if let Ok((i, piece)) = ip6_piece(input) {
input = i;
lo.push(piece);
for n in (hi.len() + 2)..8 {
if n <= 6 {
if let Ok((i, addr)) = preceded(tag(":"), ip4_network)(input) {
return Ok((i, ipv6_addr_from_pieces_ipv4(hi, lo, addr)));
}
}
if let Ok((i, piece)) = preceded(tag(":"), ip6_piece)(input) {
input = i;
lo.push(piece);
} else {
break;
}
}
}
}
Ok((input, ipv6_addr_from_pieces(hi, lo)))
}
fn ip6_piece(input: &str) -> IResult<&str, u16> {
map_res(
take_while_m_n(1, 4, is_hexdigit),
|s| u16::from_str_radix(s, 16),
)(input)
}
fn ipv6_addr_from_pieces_ipv4(hi: Vec<u16>, mut lo: Vec<u16>, addr: Ipv4Addr) -> Ipv6Addr {
assert!(hi.len() <= 6 && lo.len() <= 5);
assert!(hi.len() + lo.len() <= 6);
let [a, b, c, d] = u32::from(addr).to_be_bytes();
lo.push(u16::from_be_bytes([a, b]));
lo.push(u16::from_be_bytes([c, d]));
ipv6_addr_from_pieces(hi, lo)
}
fn ipv6_addr_from_pieces(mut hi: Vec<u16>, lo: Vec<u16>) -> Ipv6Addr {
assert!(hi.len() <= 8 && lo.len() <= 7);
assert!(hi.len() + lo.len() <= 8);
hi.resize_with(8 - lo.len(), Default::default);
hi.extend(lo);
assert!(hi.len() == 8);
match *hi {
[a, b, c, d, e, f, g, h] => Ipv6Addr::new(a, b, c, d, e, f, g, h),
_ => unreachable!(),
}
}
fn dual_cidr_length(input: &str) -> IResult<&str, DualCidrLength> {
alt((
map(
separated_pair(ip4_cidr_length, tag("/"), ip6_cidr_length),
|(len4, len6)| DualCidrLength::Both(len4, len6),
),
map(ip4_cidr_length, DualCidrLength::Ip4),
map(preceded(tag("/"), ip6_cidr_length), DualCidrLength::Ip6),
))(input)
}
fn ip4_cidr_length(input: &str) -> IResult<&str, Ip4CidrLength> {
map(preceded(tag("/"), length32), Ip4CidrLength)(input)
}
fn length32(input: &str) -> IResult<&str, u8> {
verify(
map_res(
alt((
tag("0"),
recognize(pair(
take_while_m_n(1, 1, is_positive_digit),
take_while_m_n(0, 1, is_digit),
)),
)),
u8::from_str,
),
|&len| is_ip4_cidr_length(len),
)(input)
}
fn ip6_cidr_length(input: &str) -> IResult<&str, Ip6CidrLength> {
map(preceded(tag("/"), length128), Ip6CidrLength)(input)
}
fn length128(input: &str) -> IResult<&str, u8> {
verify(
map_res(
alt((
tag("0"),
recognize(pair(
take_while_m_n(1, 1, is_positive_digit),
take_while_m_n(0, 2, is_digit),
)),
)),
u8::from_str,
),
|&len| is_ip6_cidr_length(len),
)(input)
}
fn is_positive_digit(c: char) -> bool {
matches!(c, '1'..='9')
}
fn domain_spec(input: &str) -> IResult<&str, DomainSpec> {
let (i, mut macro_string) = macro_string(input)?;
let len = input.len() - i.len();
match &mut macro_string.segments[..] {
[.., MacroStringSegment::MacroExpand(_)] => Ok((i, DomainSpec(macro_string))),
[.., MacroStringSegment::MacroLiteral(literal)] => {
let s = literal.as_ref();
if let Some(index) = s.rfind('.') {
if let Ok((ix, _)) = domain_end(&s[index..]) {
let i = &input[(len - ix.len())..];
let slen = s.len() - ix.len();
literal.0.truncate(slen);
return Ok((i, DomainSpec(macro_string)));
}
if let Some(index2) = s[..index].rfind('.') {
if let Ok((ix, _)) = domain_end(&s[index2..]) {
let i = &input[(len - ix.len())..];
let slen = s.len() - ix.len();
literal.0.truncate(slen);
return Ok((i, DomainSpec(macro_string)));
}
}
}
Err(nom::Err::Error((input, ErrorKind::Verify)))
}
_ => Err(nom::Err::Error((input, ErrorKind::Verify))),
}
}
fn domain_end(input: &str) -> IResult<&str, &str> {
verify(delimited(tag("."), toplabel, opt(tag("."))), is_toplabel)(input)
}
fn toplabel(input: &str) -> IResult<&str, &str> {
alt((
recognize(pair(alphanumeric1, many1(pair(many1(tag("-")), alphanumeric1)))),
recognize(tuple((digit0, alpha1, alphanumeric0))),
))(input)
}
impl FromStr for ExplainString {
type Err = ParseExplainStringError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match all_consuming(explain_string)(s) {
Ok((_, result)) => Ok(result),
Err(_) => Err(ParseExplainStringError),
}
}
}
fn explain_string(input: &str) -> IResult<&str, ExplainString> {
map(many0(explain_string_segment), |segments| ExplainString { segments })(input)
}
fn explain_string_segment(input: &str) -> IResult<&str, ExplainStringSegment> {
alt((
value(ExplainStringSegment::Space, take_while_m_n(1, 1, is_space)),
map(
many1(macro_string_segment),
|segments| ExplainStringSegment::MacroString(MacroString { segments }),
),
))(input)
}
fn macro_string(input: &str) -> IResult<&str, MacroString> {
map(many0(macro_string_segment), |segments| MacroString { segments })(input)
}
fn macro_string_segment(input: &str) -> IResult<&str, MacroStringSegment> {
alt((
map(macro_expand, MacroStringSegment::MacroExpand),
map(macro_literal, MacroStringSegment::MacroLiteral),
))(input)
}
fn macro_expand(input: &str) -> IResult<&str, MacroExpand> {
preceded(
tag("%"),
alt((
map(macro_, MacroExpand::Macro),
map(macro_escape, MacroExpand::Escape),
)),
)(input)
}
fn macro_(input: &str) -> IResult<&str, Macro> {
map(
delimited(
tag("{"),
tuple((
macro_letter,
opt(macro_transformer_limit),
macro_transformer_reverse,
opt(macro_delimiters),
)),
tag("}"),
),
|((kind, url_encode), limit, reverse, delimiters)| Macro {
kind,
url_encode,
limit,
reverse,
delimiters,
},
)(input)
}
fn macro_letter(input: &str) -> IResult<&str, (MacroKind, bool)> {
fn to_macro_kind(c: char) -> MacroKind {
use MacroKind::*;
match c {
's' | 'S' => Sender,
'l' | 'L' => LocalPart,
'o' | 'O' => DomainPart,
'd' | 'D' => Domain,
'i' | 'I' => Ip,
'p' | 'P' => ValidatedDomain,
'h' | 'H' => HeloDomain,
'c' | 'C' => PrettyIp,
'r' | 'R' => Receiver,
't' | 'T' => Timestamp,
'v' | 'V' => VersionLabel,
_ => unreachable!(),
}
}
alt((
map(one_of("slodiphcrtv"), |c| (to_macro_kind(c), false)),
map(one_of("SLODIPHCRTV"), |c| (to_macro_kind(c), true)),
))(input)
}
fn macro_transformer_limit(input: &str) -> IResult<&str, NonZeroU8> {
map_res(digit1, NonZeroU8::from_str)(input)
}
fn macro_transformer_reverse(input: &str) -> IResult<&str, bool> {
map(opt(tag_no_case("r")), |r| r.is_some())(input)
}
fn macro_delimiters(input: &str) -> IResult<&str, Delimiters> {
map(take_while1(is_delimiter_char), |s| Delimiters(String::from(s)))(input)
}
fn macro_escape(input: &str) -> IResult<&str, Escape> {
map(one_of("%_-"), |c| match c {
'%' => Escape::Percent,
'_' => Escape::Space,
'-' => Escape::UrlEncodedSpace,
_ => unreachable!(),
})(input)
}
fn macro_literal(input: &str) -> IResult<&str, MacroLiteral> {
map(take_while1(is_macro_literal_char), |s| MacroLiteral(String::from(s)))(input)
}
fn is_space(c: char) -> bool {
c == ' '
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn record_ok() {
assert_eq!(
record("v=spf1 mx -all"),
Ok((
"",
Record {
terms: vec![
Term::Directive(Directive {
qualifier: None,
mechanism: Mechanism::Mx(Mx {
domain_spec: None,
prefix_len: None
}),
}),
Term::Directive(Directive {
qualifier: Some(Qualifier::Fail),
mechanism: Mechanism::All
})
]
}
))
);
assert_eq!(
record("v=SPF1 ip4:128.1.1.4/16 -all "),
Ok((
"",
Record {
terms: vec![
Term::Directive(Directive {
qualifier: None,
mechanism: Mechanism::Ip4(Ip4 {
addr: Ipv4Addr::new(128, 1, 1, 4),
prefix_len: Some(Ip4CidrLength::new(16).unwrap())
})
}),
Term::Directive(Directive {
qualifier: Some(Qualifier::Fail),
mechanism: Mechanism::All
})
]
}
))
);
assert_eq!(
record("v=SPF1 +all all -all"),
Ok((
"",
Record {
terms: vec![
Term::Directive(Directive {
qualifier: Some(Qualifier::Pass),
mechanism: Mechanism::All
}),
Term::Directive(Directive {
qualifier: None,
mechanism: Mechanism::All
}),
Term::Directive(Directive {
qualifier: Some(Qualifier::Fail),
mechanism: Mechanism::All
}),
],
}
))
);
}
#[test]
fn record_all_consuming() {
assert_eq!("v=spf1 ".parse::<Record>(), Ok(Record { terms: vec![] }));
assert_eq!("v=spf1 invalid trailer".parse::<Record>(), Err(ParseRecordError));
}
#[test]
fn mechanism_ok() {
assert_eq!(
mechanism("a:smtp.myhost.org/30"),
Ok((
"",
Mechanism::A(A {
domain_spec: Some(
DomainSpec::new(MacroString {
segments: vec![MacroStringSegment::MacroLiteral(
MacroLiteral::new("smtp.myhost.org").unwrap()
)]
})
.unwrap()
),
prefix_len: Some(Ip4CidrLength::new(30).unwrap().into()),
})
))
);
}
#[test]
fn mechanism_ip4_ok() {
assert_eq!(
mechanism_ip4("ip4:127.0.0.0/8"),
Ok((
"",
Mechanism::Ip4(Ip4 {
addr: Ipv4Addr::new(127, 0, 0, 0),
prefix_len: Some(Ip4CidrLength::new(8).unwrap())
})
))
);
assert_eq!(
mechanism_ip4("ip4:253.1.23.0"),
Ok((
"",
Mechanism::Ip4(Ip4 {
addr: Ipv4Addr::new(253, 1, 23, 0),
prefix_len: None,
})
))
);
}
#[test]
fn ip6_addr4_ok() {
assert_eq!(
ip6_network("1:1:1:1:1:1:127.0.0.1:9"),
Ok((":9", Ipv6Addr::new(1, 1, 1, 1, 1, 1, 0x7f00, 1)))
);
assert_eq!(
ip6_network("1:1:1:1:1:1:1:1:9"),
Ok((":9", Ipv6Addr::new(1, 1, 1, 1, 1, 1, 1, 1)))
);
assert_eq!(
ip6_network("1:1:1:1:1:1:1::9"),
Ok(("9", Ipv6Addr::new(1, 1, 1, 1, 1, 1, 1, 0)))
);
assert_eq!(
ip6_network("1:1:1:1:1:1::x"),
Ok(("x", Ipv6Addr::new(1, 1, 1, 1, 1, 1, 0, 0)))
);
assert_eq!(
ip6_network("1::x"),
Ok(("x", Ipv6Addr::new(1, 0, 0, 0, 0, 0, 0, 0)))
);
assert_eq!(
ip6_network("::x"),
Ok(("x", Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0)))
);
assert_eq!(
ip6_network("::127.0.0.1:9"),
Ok((":9", Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0x7f00, 1)))
);
assert_eq!(
ip6_network("1::127.0.0.1:9"),
Ok((":9", Ipv6Addr::new(1, 0, 0, 0, 0, 0, 0x7f00, 1)))
);
assert_eq!(
ip6_network("1:1:1:1:1::127.0.0.1:9"),
Ok((":9", Ipv6Addr::new(1, 1, 1, 1, 1, 0, 0x7f00, 1)))
);
assert_eq!(
ip6_network("1::1x"),
Ok(("x", Ipv6Addr::new(1, 0, 0, 0, 0, 0, 0, 1)))
);
assert_eq!(
ip6_network("1:1::1:1:1x"),
Ok(("x", Ipv6Addr::new(1, 1, 0, 0, 0, 1, 1, 1)))
);
assert_eq!(
ip6_network("::1:127.0.0.1:9"),
Ok((":9", Ipv6Addr::new(0, 0, 0, 0, 0, 1, 0x7f00, 1)))
);
assert_eq!(
ip6_network("1:1::1:1:1:127.0.0.1:9"),
Ok((":9", Ipv6Addr::new(1, 1, 0, 1, 1, 1, 0x7f00, 1)))
);
assert_eq!(
ip6_network("::1:1:1:1:1:127.0.0.1:9"),
Ok((":9", Ipv6Addr::new(0, 1, 1, 1, 1, 1, 0x7f00, 1)))
);
assert_eq!(
ip6_network("1:1::1:1:1:1:1:9"),
Ok((":9", Ipv6Addr::new(1, 1, 0, 1, 1, 1, 1, 1)))
);
}
#[test]
fn dual_cidr_length_ok() {
assert_eq!(
dual_cidr_length("/3"),
Ok(("", DualCidrLength::Ip4(Ip4CidrLength::new(3).unwrap())))
);
assert_eq!(
dual_cidr_length("//45"),
Ok(("", DualCidrLength::Ip6(Ip6CidrLength::new(45).unwrap())))
);
assert_eq!(
dual_cidr_length("/3//45"),
Ok((
"",
DualCidrLength::Both(
Ip4CidrLength::new(3).unwrap(),
Ip6CidrLength::new(45).unwrap()
)
))
);
}
#[test]
fn length32_ok() {
assert_eq!(length32("01"), Ok(("1", 0)));
assert_eq!(length32("1x"), Ok(("x", 1)));
assert_eq!(length32("111"), Ok(("1", 11)));
assert_eq!(length32("345"), Err(nom::Err::Error(("345", ErrorKind::Verify))));
}
#[test]
fn length128_ok() {
assert_eq!(length128("01"), Ok(("1", 0)));
assert_eq!(length128("1x"), Ok(("x", 1)));
assert_eq!(length128("11x"), Ok(("x", 11)));
assert_eq!(length128("111x"), Ok(("x", 111)));
assert_eq!(length128("2345"), Err(nom::Err::Error(("2345", ErrorKind::Verify))));
assert_eq!(length128("3456"), Err(nom::Err::Error(("3456", ErrorKind::MapRes))));
}
#[test]
fn domain_spec_ok() {
assert_eq!(
domain_spec("www.gluet.ch"),
Ok((
"",
DomainSpec::new(MacroString {
segments: vec![MacroStringSegment::MacroLiteral(
MacroLiteral::new("www.gluet.ch").unwrap()
)]
})
.unwrap()
))
);
assert_eq!(
domain_spec("www.gluet.ch."),
Ok((
"",
DomainSpec::new(MacroString {
segments: vec![MacroStringSegment::MacroLiteral(
MacroLiteral::new("www.gluet.ch.").unwrap()
)]
})
.unwrap()
))
);
assert_eq!(
domain_spec("www.gluet.234"),
Ok((
"234",
DomainSpec::new(MacroString {
segments: vec![MacroStringSegment::MacroLiteral(
MacroLiteral::new("www.gluet.").unwrap()
)]
})
.unwrap()
))
);
assert_eq!(domain_spec("1312"), Err(nom::Err::Error(("1312", ErrorKind::Verify))));
assert_eq!(
domain_spec("smtp.myhost.org/30"),
Ok((
"/30",
DomainSpec::new(MacroString {
segments: vec![MacroStringSegment::MacroLiteral(
MacroLiteral::new("smtp.myhost.org").unwrap()
)]
})
.unwrap()
))
);
}
#[test]
fn toplabel_ok() {
assert_eq!(toplabel("com"), Ok(("", "com")));
assert_eq!(toplabel("c2m"), Ok(("", "c2m")));
assert_eq!(toplabel("2m"), Ok(("", "2m")));
assert_eq!(toplabel("m2"), Ok(("", "m2")));
assert_eq!(toplabel("22"), Err(nom::Err::Error(("", ErrorKind::Alpha))));
assert_eq!(toplabel(""), Err(nom::Err::Error(("", ErrorKind::Alpha))));
assert_eq!(toplabel("com-"), Ok(("-", "com")));
assert_eq!(toplabel("a-com"), Ok(("", "a-com")));
assert_eq!(toplabel("a--com"), Ok(("", "a--com")));
assert_eq!(toplabel("a-c--c-com"), Ok(("", "a-c--c-com")));
assert_eq!(toplabel("-ab"), Err(nom::Err::Error(("-ab", ErrorKind::Alpha))));
}
#[test]
fn explain_string_ok() {
assert_eq!(
explain_string("what %%g"),
Ok((
"",
ExplainString {
segments: vec![
ExplainStringSegment::MacroString(MacroString {
segments: vec![MacroStringSegment::MacroLiteral(
MacroLiteral::new("what").unwrap()
)]
}),
ExplainStringSegment::Space,
ExplainStringSegment::MacroString(MacroString {
segments: vec![
MacroStringSegment::MacroExpand(MacroExpand::Escape(
Escape::Percent
)),
MacroStringSegment::MacroLiteral(
MacroLiteral::new("g").unwrap()
),
]
}),
]
}
))
);
}
#[test]
fn macro_string_ok() {
let mut macro_ = Macro::new(MacroKind::DomainPart);
macro_.set_reverse(true);
assert_eq!(
macro_string("%{or}%%org"),
Ok((
"",
MacroString {
segments: vec![
MacroStringSegment::MacroExpand(MacroExpand::Macro(macro_)),
MacroStringSegment::MacroExpand(MacroExpand::Escape(Escape::Percent)),
MacroStringSegment::MacroLiteral(MacroLiteral::new("org").unwrap()),
]
}
))
);
}
#[test]
fn macro_expand_ok() {
assert_eq!(
macro_expand("%{o}x"),
Ok(("x", MacroExpand::Macro(MacroKind::DomainPart.into())))
);
let mut macro_ = Macro::new(MacroKind::DomainPart);
macro_.set_limit(NonZeroU8::new(4).unwrap());
macro_.set_reverse(true);
assert_eq!(macro_expand("%{o04r}"), Ok(("", MacroExpand::Macro(macro_))));
assert_eq!(
macro_expand("%{o0444r}"),
Err(nom::Err::Error(("{o0444r}", ErrorKind::OneOf)))
);
}
#[test]
fn macro_letter_ok() {
assert_eq!(macro_letter("d"), Ok(("", (MacroKind::Domain, false))));
assert_eq!(macro_letter("D"), Ok(("", (MacroKind::Domain, true))));
assert_eq!(macro_letter("x"), Err(nom::Err::Error(("x", ErrorKind::OneOf))));
}
#[test]
fn macro_transformer_limit_ok() {
assert_eq!(macro_transformer_limit(""), Err(nom::Err::Error(("", ErrorKind::Digit))));
assert_eq!(macro_transformer_limit("0"), Err(nom::Err::Error(("0", ErrorKind::MapRes))));
assert_eq!(macro_transformer_limit("3"), Ok(("", NonZeroU8::new(3).unwrap())));
assert_eq!(macro_transformer_limit("333"), Err(nom::Err::Error(("333", ErrorKind::MapRes))));
}
#[test]
fn macro_transformer_reverse_ok() {
assert_eq!(macro_transformer_reverse(""), Ok(("", false)));
assert_eq!(macro_transformer_reverse("r"), Ok(("", true)));
}
#[test]
fn macro_delimiters_ok() {
assert_eq!(macro_delimiters(""), Err(nom::Err::Error(("", ErrorKind::TakeWhile1))));
assert_eq!(macro_delimiters("."), Ok(("", Delimiters::new(".").unwrap())));
assert_eq!(macro_delimiters("-__"), Ok(("", Delimiters::new("-__").unwrap())));
}
}