use std::fmt;
#[derive(Debug)]
pub enum Error {
Network(String),
CertificateError(String),
AuthenticationFailure,
NoRecord,
Protocol(String),
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Network(msg) => write!(f, "DNSCrypt network error: {msg}"),
Self::CertificateError(msg) => write!(f, "DNSCrypt certificate error: {msg}"),
Self::AuthenticationFailure => {
write!(f, "DNSCrypt authentication failure (Poly1305 tag mismatch)")
}
Self::NoRecord => write!(f, "DNSCrypt: no A record found in response"),
Self::Protocol(msg) => write!(f, "DNSCrypt protocol error: {msg}"),
}
}
}
impl std::error::Error for Error {}
impl From<String> for Error {
fn from(s: String) -> Self {
if s.contains("authentication") || s.contains("Poly1305") {
Self::AuthenticationFailure
} else if s.contains("TCP") || s.contains("UDP") || s.contains("socket") {
Self::Network(s)
} else if s.contains("cert") || s.contains("Cert") || s.contains("serial") {
Self::CertificateError(s)
} else {
Self::Protocol(s)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_display_messages() {
assert_eq!(
Error::Network("timed out".to_string()).to_string(),
"DNSCrypt network error: timed out"
);
assert_eq!(
Error::CertificateError("expired".to_string()).to_string(),
"DNSCrypt certificate error: expired"
);
assert_eq!(
Error::AuthenticationFailure.to_string(),
"DNSCrypt authentication failure (Poly1305 tag mismatch)"
);
assert_eq!(
Error::NoRecord.to_string(),
"DNSCrypt: no A record found in response"
);
assert_eq!(
Error::Protocol("bad magic".to_string()).to_string(),
"DNSCrypt protocol error: bad magic"
);
}
#[test]
fn test_from_string_classification() {
assert!(matches!(
Error::from("authentication failed".to_string()),
Error::AuthenticationFailure
));
assert!(matches!(
Error::from("Poly1305 tag mismatch".to_string()),
Error::AuthenticationFailure
));
assert!(matches!(
Error::from("TCP connect failed: oops".to_string()),
Error::Network(_)
));
assert!(matches!(
Error::from("UDP send failed: oops".to_string()),
Error::Network(_)
));
assert!(matches!(
Error::from("failed to bind socket".to_string()),
Error::Network(_)
));
assert!(matches!(
Error::from("cert serial 1 < cached 2".to_string()),
Error::CertificateError(_)
));
assert!(matches!(
Error::from("Certificate expired".to_string()),
Error::CertificateError(_)
));
assert!(matches!(
Error::from("duplicate serial number".to_string()),
Error::CertificateError(_)
));
assert!(matches!(
Error::from("something else entirely".to_string()),
Error::Protocol(_)
));
}
}