1use std::fmt;
4
5#[derive(Debug, Clone, PartialEq, Eq)]
7pub enum SpfResult {
8 None,
10 Pass,
12 Fail,
14 SoftFail,
16 Neutral,
18 PermError,
22 TempError,
25}
26
27impl SpfResult {
28 pub fn as_str(&self) -> &'static str {
31 match self {
32 SpfResult::None => "none",
33 SpfResult::Pass => "pass",
34 SpfResult::Fail => "fail",
35 SpfResult::SoftFail => "softfail",
36 SpfResult::Neutral => "neutral",
37 SpfResult::PermError => "permerror",
38 SpfResult::TempError => "temperror",
39 }
40 }
41}
42
43impl fmt::Display for SpfResult {
44 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
45 f.write_str(self.as_str())
46 }
47}
48
49#[derive(Debug, Clone, PartialEq, Eq)]
56pub enum SpfError {
57 DnsTempError(String),
59 DnsPermError(String),
63 InvalidRecord(String),
65 TooManyLookups,
67 TooMuchRecursion,
69 MultipleRecords,
72}
73
74impl fmt::Display for SpfError {
75 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
76 match self {
77 SpfError::DnsTempError(s) => write!(f, "DNS temporary error: {s}"),
78 SpfError::DnsPermError(s) => write!(f, "DNS permanent error: {s}"),
79 SpfError::InvalidRecord(s) => write!(f, "invalid SPF record: {s}"),
80 SpfError::TooManyLookups => f.write_str("too many DNS lookups (>10)"),
81 SpfError::TooMuchRecursion => f.write_str("too much include: recursion"),
82 SpfError::MultipleRecords => f.write_str("multiple v=spf1 records at domain"),
83 }
84 }
85}
86
87impl std::error::Error for SpfError {}
88
89impl SpfError {
90 pub fn to_result(&self) -> SpfResult {
93 match self {
94 SpfError::DnsTempError(_) => SpfResult::TempError,
95 SpfError::DnsPermError(_)
96 | SpfError::InvalidRecord(_)
97 | SpfError::TooManyLookups
98 | SpfError::TooMuchRecursion
99 | SpfError::MultipleRecords => SpfResult::PermError,
100 }
101 }
102}
103
104#[cfg(test)]
105mod tests {
106 use super::*;
107
108 #[test]
109 fn spf_result_as_str_matches_rfc_7001() {
110 assert_eq!(SpfResult::None.as_str(), "none");
111 assert_eq!(SpfResult::Pass.as_str(), "pass");
112 assert_eq!(SpfResult::Fail.as_str(), "fail");
113 assert_eq!(SpfResult::SoftFail.as_str(), "softfail");
114 assert_eq!(SpfResult::Neutral.as_str(), "neutral");
115 assert_eq!(SpfResult::PermError.as_str(), "permerror");
116 assert_eq!(SpfResult::TempError.as_str(), "temperror");
117 }
118
119 #[test]
120 fn spf_result_display_matches_as_str() {
121 assert_eq!(format!("{}", SpfResult::Pass), "pass");
122 assert_eq!(format!("{}", SpfResult::SoftFail), "softfail");
123 }
124
125 #[test]
126 fn spf_error_to_result_classification() {
127 assert_eq!(
128 SpfError::DnsTempError("timeout".into()).to_result(),
129 SpfResult::TempError
130 );
131 assert_eq!(
132 SpfError::DnsPermError("nxdomain".into()).to_result(),
133 SpfResult::PermError
134 );
135 assert_eq!(
136 SpfError::InvalidRecord("bad mechanism".into()).to_result(),
137 SpfResult::PermError
138 );
139 assert_eq!(SpfError::TooManyLookups.to_result(), SpfResult::PermError);
140 assert_eq!(SpfError::TooMuchRecursion.to_result(), SpfResult::PermError);
141 assert_eq!(SpfError::MultipleRecords.to_result(), SpfResult::PermError);
142 }
143
144 #[test]
145 fn spf_error_display_includes_context() {
146 let e = SpfError::DnsTempError("connection refused".into());
147 let s = format!("{e}");
148 assert!(s.contains("connection refused"));
149 }
150}