hickory_resolver/
error.rs

1// Copyright 2015-2023 Benjamin Fry <benjaminfry@me.com>
2//
3// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
4// https://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
5// https://opensource.org/licenses/MIT>, at your option. This file may not be
6// copied, modified, or distributed except according to those terms.
7
8//! Error types for the crate
9
10use std::{fmt, io, sync};
11
12use thiserror::Error;
13
14#[cfg(feature = "backtrace")]
15use crate::proto::{ExtBacktrace, trace};
16use crate::proto::{
17    ProtoError, ProtoErrorKind,
18    rr::{Record, rdata::SOA},
19    xfer::retry_dns_handle::RetryableError,
20};
21
22#[allow(clippy::large_enum_variant)]
23/// The error kind for errors that get returned in the crate
24#[derive(Debug, Error)]
25#[non_exhaustive]
26pub enum ResolveErrorKind {
27    /// An error with an arbitrary message, referenced as &'static str
28    #[error("{0}")]
29    Message(&'static str),
30
31    /// An error with an arbitrary message, stored as String
32    #[error("{0}")]
33    Msg(String),
34
35    /// An error got returned by the hickory-proto crate
36    #[error("proto error: {0}")]
37    Proto(#[from] ProtoError),
38}
39
40impl Clone for ResolveErrorKind {
41    fn clone(&self) -> Self {
42        use self::ResolveErrorKind::*;
43        match self {
44            Message(msg) => Message(msg),
45            Msg(msg) => Msg(msg.clone()),
46            // foreign
47            Proto(proto) => Self::from(proto.clone()),
48        }
49    }
50}
51
52/// The error type for errors that get returned in the crate
53#[derive(Debug, Clone, Error)]
54pub struct ResolveError {
55    pub(crate) kind: ResolveErrorKind,
56    #[cfg(feature = "backtrace")]
57    backtrack: Option<ExtBacktrace>,
58}
59
60impl ResolveError {
61    /// Get the kind of the error
62    pub fn kind(&self) -> &ResolveErrorKind {
63        &self.kind
64    }
65
66    /// Take the kind of the error
67    pub fn into_kind(self) -> ResolveErrorKind {
68        self.kind
69    }
70
71    /// If this is an underlying proto error, return that
72    pub fn proto(&self) -> Option<&ProtoError> {
73        match &self.kind {
74            ResolveErrorKind::Proto(proto) => Some(proto),
75            _ => None,
76        }
77    }
78
79    /// Returns true if the domain does not exist
80    pub fn is_nx_domain(&self) -> bool {
81        self.proto()
82            .map(|proto| proto.is_nx_domain())
83            .unwrap_or(false)
84    }
85
86    /// Returns true if no records were returned
87    pub fn is_no_records_found(&self) -> bool {
88        self.proto()
89            .map(|proto| proto.is_no_records_found())
90            .unwrap_or(false)
91    }
92
93    /// Returns the SOA record, if the error contains one
94    pub fn into_soa(self) -> Option<Box<Record<SOA>>> {
95        match self.kind {
96            ResolveErrorKind::Proto(proto) => proto.into_soa(),
97            _ => None,
98        }
99    }
100}
101
102impl RetryableError for ResolveError {
103    fn should_retry(&self) -> bool {
104        match self.kind() {
105            ResolveErrorKind::Message(_) | ResolveErrorKind::Msg(_) => false,
106            ResolveErrorKind::Proto(proto) => proto.should_retry(),
107        }
108    }
109
110    fn attempted(&self) -> bool {
111        match self.kind() {
112            ResolveErrorKind::Proto(e) => e.attempted(),
113            _ => true,
114        }
115    }
116}
117
118impl fmt::Display for ResolveError {
119    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
120        cfg_if::cfg_if! {
121            if #[cfg(feature = "backtrace")] {
122                if let Some(backtrace) = &self.backtrack {
123                    fmt::Display::fmt(&self.kind, f)?;
124                    fmt::Debug::fmt(backtrace, f)
125                } else {
126                    fmt::Display::fmt(&self.kind, f)
127                }
128            } else {
129                fmt::Display::fmt(&self.kind, f)
130            }
131        }
132    }
133}
134
135impl From<ResolveErrorKind> for ResolveError {
136    fn from(kind: ResolveErrorKind) -> Self {
137        Self {
138            kind,
139            #[cfg(feature = "backtrace")]
140            backtrack: trace!(),
141        }
142    }
143}
144
145impl From<&'static str> for ResolveError {
146    fn from(msg: &'static str) -> Self {
147        ResolveErrorKind::Message(msg).into()
148    }
149}
150
151impl TryFrom<ResolveError> for ProtoErrorKind {
152    type Error = ResolveError;
153    fn try_from(error: ResolveError) -> Result<Self, Self::Error> {
154        match error.kind {
155            ResolveErrorKind::Proto(p) => Ok(*p.kind),
156            _ => Err(error),
157        }
158    }
159}
160
161#[cfg(target_os = "windows")]
162#[cfg(feature = "system-config")]
163impl From<ipconfig::error::Error> for ResolveError {
164    fn from(e: ipconfig::error::Error) -> ResolveError {
165        ResolveErrorKind::Msg(format!("failed to read from registry: {}", e)).into()
166    }
167}
168
169impl From<String> for ResolveError {
170    fn from(msg: String) -> Self {
171        ResolveErrorKind::Msg(msg).into()
172    }
173}
174
175impl From<io::Error> for ResolveError {
176    fn from(e: io::Error) -> Self {
177        ResolveErrorKind::from(ProtoError::from(e)).into()
178    }
179}
180
181impl From<ProtoError> for ResolveError {
182    fn from(e: ProtoError) -> Self {
183        ResolveErrorKind::Proto(e).into()
184    }
185}
186
187impl From<ResolveError> for io::Error {
188    fn from(e: ResolveError) -> Self {
189        Self::new(io::ErrorKind::Other, e)
190    }
191}
192
193impl<T> From<sync::PoisonError<T>> for ResolveError {
194    fn from(e: sync::PoisonError<T>) -> Self {
195        ResolveErrorKind::Msg(format!("lock was poisoned, this is non-recoverable: {e}")).into()
196    }
197}