use std::fmt;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum SpfResult {
None,
Pass,
Fail,
SoftFail,
Neutral,
PermError,
TempError,
}
impl SpfResult {
pub fn as_str(&self) -> &'static str {
match self {
SpfResult::None => "none",
SpfResult::Pass => "pass",
SpfResult::Fail => "fail",
SpfResult::SoftFail => "softfail",
SpfResult::Neutral => "neutral",
SpfResult::PermError => "permerror",
SpfResult::TempError => "temperror",
}
}
}
impl fmt::Display for SpfResult {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum SpfError {
DnsTempError(String),
DnsPermError(String),
InvalidRecord(String),
TooManyLookups,
TooMuchRecursion,
MultipleRecords,
}
impl fmt::Display for SpfError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
SpfError::DnsTempError(s) => write!(f, "DNS temporary error: {s}"),
SpfError::DnsPermError(s) => write!(f, "DNS permanent error: {s}"),
SpfError::InvalidRecord(s) => write!(f, "invalid SPF record: {s}"),
SpfError::TooManyLookups => f.write_str("too many DNS lookups (>10)"),
SpfError::TooMuchRecursion => f.write_str("too much include: recursion"),
SpfError::MultipleRecords => f.write_str("multiple v=spf1 records at domain"),
}
}
}
impl std::error::Error for SpfError {}
impl SpfError {
pub fn to_result(&self) -> SpfResult {
match self {
SpfError::DnsTempError(_) => SpfResult::TempError,
SpfError::DnsPermError(_)
| SpfError::InvalidRecord(_)
| SpfError::TooManyLookups
| SpfError::TooMuchRecursion
| SpfError::MultipleRecords => SpfResult::PermError,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn spf_result_as_str_matches_rfc_7001() {
assert_eq!(SpfResult::None.as_str(), "none");
assert_eq!(SpfResult::Pass.as_str(), "pass");
assert_eq!(SpfResult::Fail.as_str(), "fail");
assert_eq!(SpfResult::SoftFail.as_str(), "softfail");
assert_eq!(SpfResult::Neutral.as_str(), "neutral");
assert_eq!(SpfResult::PermError.as_str(), "permerror");
assert_eq!(SpfResult::TempError.as_str(), "temperror");
}
#[test]
fn spf_result_display_matches_as_str() {
assert_eq!(format!("{}", SpfResult::Pass), "pass");
assert_eq!(format!("{}", SpfResult::SoftFail), "softfail");
}
#[test]
fn spf_error_to_result_classification() {
assert_eq!(
SpfError::DnsTempError("timeout".into()).to_result(),
SpfResult::TempError
);
assert_eq!(
SpfError::DnsPermError("nxdomain".into()).to_result(),
SpfResult::PermError
);
assert_eq!(
SpfError::InvalidRecord("bad mechanism".into()).to_result(),
SpfResult::PermError
);
assert_eq!(SpfError::TooManyLookups.to_result(), SpfResult::PermError);
assert_eq!(SpfError::TooMuchRecursion.to_result(), SpfResult::PermError);
assert_eq!(SpfError::MultipleRecords.to_result(), SpfResult::PermError);
}
#[test]
fn spf_error_display_includes_context() {
let e = SpfError::DnsTempError("connection refused".into());
let s = format!("{e}");
assert!(s.contains("connection refused"));
}
}