1use std::fmt;
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub enum DnsError {
13 Timeout,
15 ServerFailure,
17 Refused,
19 Malformed,
21 NetworkError,
23 InvalidInput,
25}
26
27impl DnsError {
28 pub fn code(&self) -> &'static str {
32 match self {
33 Self::Timeout => "dns:timeout",
34 Self::ServerFailure => "dns:server_failure",
35 Self::Refused => "dns:refused",
36 Self::Malformed => "dns:malformed",
37 Self::NetworkError => "dns:network_error",
38 Self::InvalidInput => "dns:invalid_input",
39 }
40 }
41}
42
43impl fmt::Display for DnsError {
44 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
45 match self {
46 Self::Timeout => write!(f, "dns query timed out"),
47 Self::ServerFailure => write!(f, "dns server failure"),
48 Self::Refused => write!(f, "dns query refused"),
49 Self::Malformed => write!(f, "dns response malformed"),
50 Self::NetworkError => write!(f, "dns network error"),
51 Self::InvalidInput => write!(f, "invalid dns input"),
52 }
53 }
54}
55
56impl std::error::Error for DnsError {}
57
58#[cfg(test)]
59mod tests {
60 use super::*;
61 use crate::Error;
62
63 #[test]
64 fn all_variants_have_unique_codes() {
65 let variants = [
66 DnsError::Timeout,
67 DnsError::ServerFailure,
68 DnsError::Refused,
69 DnsError::Malformed,
70 DnsError::NetworkError,
71 DnsError::InvalidInput,
72 ];
73 let mut codes: Vec<&str> = variants.iter().map(|v| v.code()).collect();
74 let len_before = codes.len();
75 codes.sort();
76 codes.dedup();
77 assert_eq!(codes.len(), len_before, "duplicate error codes found");
78 }
79
80 #[test]
81 fn all_codes_start_with_dns_prefix() {
82 let variants = [
83 DnsError::Timeout,
84 DnsError::ServerFailure,
85 DnsError::NetworkError,
86 ];
87 for v in &variants {
88 assert!(
89 v.code().starts_with("dns:"),
90 "code {} missing prefix",
91 v.code()
92 );
93 }
94 }
95
96 #[test]
97 fn display_is_human_readable() {
98 assert_eq!(DnsError::Timeout.to_string(), "dns query timed out");
99 assert_eq!(DnsError::ServerFailure.to_string(), "dns server failure");
100 assert_eq!(DnsError::Malformed.to_string(), "dns response malformed");
101 }
102
103 #[test]
104 fn recoverable_via_source_as() {
105 let err = Error::bad_gateway("dns server failure")
106 .chain(DnsError::ServerFailure)
107 .with_code(DnsError::ServerFailure.code());
108 let dns_err = err.source_as::<DnsError>();
109 assert_eq!(dns_err, Some(&DnsError::ServerFailure));
110 assert_eq!(err.error_code(), Some("dns:server_failure"));
111 }
112
113 #[test]
114 fn timeout_maps_to_gateway_timeout() {
115 let err = Error::gateway_timeout("dns query timed out")
116 .chain(DnsError::Timeout)
117 .with_code(DnsError::Timeout.code());
118 assert_eq!(err.status(), http::StatusCode::GATEWAY_TIMEOUT);
119 assert_eq!(err.error_code(), Some("dns:timeout"));
120 }
121}