1use std::fmt;
4
5#[derive(Debug, Clone, PartialEq, Eq)]
8pub enum DkimResult {
9 None,
11 Pass,
13 Fail,
16 Policy,
19 Neutral,
22 TempError,
24 PermError,
27}
28
29impl DkimResult {
30 pub fn as_str(&self) -> &'static str {
32 match self {
33 DkimResult::None => "none",
34 DkimResult::Pass => "pass",
35 DkimResult::Fail => "fail",
36 DkimResult::Policy => "policy",
37 DkimResult::Neutral => "neutral",
38 DkimResult::TempError => "temperror",
39 DkimResult::PermError => "permerror",
40 }
41 }
42}
43
44impl fmt::Display for DkimResult {
45 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
46 f.write_str(self.as_str())
47 }
48}
49
50#[derive(Debug, Clone, PartialEq, Eq)]
52pub enum DkimError {
53 MissingHeader,
55 MissingTag(String),
57 InvalidTag(String),
59 InvalidBase64(String),
61 DnsTempError(String),
63 DnsPermError(String),
65 InvalidKey(String),
67 UnsupportedAlgorithm(String),
69 UnsupportedCanon(String),
71 BodyHashMismatch,
73 SignatureMismatch,
76 Expired,
78}
79
80impl fmt::Display for DkimError {
81 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
82 match self {
83 DkimError::MissingHeader => f.write_str("no DKIM-Signature header"),
84 DkimError::MissingTag(t) => write!(f, "missing required tag {t}="),
85 DkimError::InvalidTag(t) => write!(f, "invalid tag: {t}"),
86 DkimError::InvalidBase64(t) => write!(f, "invalid base64 in tag {t}"),
87 DkimError::DnsTempError(s) => write!(f, "DNS temporary error: {s}"),
88 DkimError::DnsPermError(s) => write!(f, "DNS permanent error: {s}"),
89 DkimError::InvalidKey(s) => write!(f, "invalid public key: {s}"),
90 DkimError::UnsupportedAlgorithm(a) => write!(f, "unsupported algorithm: {a}"),
91 DkimError::UnsupportedCanon(c) => write!(f, "unsupported canonicalization: {c}"),
92 DkimError::BodyHashMismatch => f.write_str("body hash mismatch"),
93 DkimError::SignatureMismatch => f.write_str("signature mismatch"),
94 DkimError::Expired => f.write_str("signature expired"),
95 }
96 }
97}
98
99impl std::error::Error for DkimError {}
100
101impl DkimError {
102 pub fn to_result(&self) -> DkimResult {
104 match self {
105 DkimError::MissingHeader => DkimResult::None,
106 DkimError::DnsTempError(_) => DkimResult::TempError,
107 DkimError::DnsPermError(_)
108 | DkimError::InvalidKey(_)
109 | DkimError::UnsupportedAlgorithm(_)
110 | DkimError::UnsupportedCanon(_)
111 | DkimError::Expired => DkimResult::PermError,
112 DkimError::MissingTag(_) | DkimError::InvalidTag(_) | DkimError::InvalidBase64(_) => {
113 DkimResult::Neutral
114 }
115 DkimError::BodyHashMismatch | DkimError::SignatureMismatch => DkimResult::Fail,
116 }
117 }
118}
119
120#[cfg(test)]
121mod tests {
122 use super::*;
123
124 #[test]
125 fn result_as_str_matches_rfc_8601() {
126 assert_eq!(DkimResult::None.as_str(), "none");
127 assert_eq!(DkimResult::Pass.as_str(), "pass");
128 assert_eq!(DkimResult::Fail.as_str(), "fail");
129 assert_eq!(DkimResult::Neutral.as_str(), "neutral");
130 assert_eq!(DkimResult::TempError.as_str(), "temperror");
131 assert_eq!(DkimResult::PermError.as_str(), "permerror");
132 }
133
134 #[test]
135 fn error_to_result_classification() {
136 assert_eq!(DkimError::MissingHeader.to_result(), DkimResult::None);
137 assert_eq!(
138 DkimError::DnsTempError("x".into()).to_result(),
139 DkimResult::TempError
140 );
141 assert_eq!(
142 DkimError::DnsPermError("x".into()).to_result(),
143 DkimResult::PermError
144 );
145 assert_eq!(
146 DkimError::MissingTag("a".into()).to_result(),
147 DkimResult::Neutral
148 );
149 assert_eq!(DkimError::BodyHashMismatch.to_result(), DkimResult::Fail);
150 assert_eq!(DkimError::SignatureMismatch.to_result(), DkimResult::Fail);
151 assert_eq!(DkimError::Expired.to_result(), DkimResult::PermError);
152 }
153
154 #[test]
155 fn display_contains_context() {
156 let e = DkimError::MissingTag("a".into());
157 let s = format!("{e}");
158 assert!(s.contains('a'));
159 }
160}