1
//! Declare tor client specific errors.
2

            
3
use std::fmt::{self, Display};
4
use std::sync::Arc;
5

            
6
use futures::task::SpawnError;
7

            
8
use thiserror::Error;
9
use tor_circmgr::TargetPorts;
10
use tor_error::{ErrorKind, HasKind};
11

            
12
use crate::TorAddrError;
13

            
14
/// Main high-level error type for the Arti Tor client
15
///
16
/// If you need to handle different types of errors differently, use the
17
/// [`kind`](`tor_error::HasKind::kind`) trait method to check what kind of
18
/// error it is.
19
///
20
/// Note that although this type implements that standard
21
/// [`Error`](std::error::Error) trait, the output of that trait's methods are
22
/// not covered by semantic versioning.  Specifically: you should not rely on
23
/// the specific output of `Display`, `Debug`, or `Error::source()` when run on
24
/// this type; it may change between patch versions without notification.
25
#[derive(Error, Clone, Debug)]
26
pub struct Error {
27
    /// The actual error.
28
    ///
29
    /// This field is exposed  via the `detail()` method only if the the
30
    /// `error_detail` feature is enabled. Using it will void your semver
31
    /// guarantee.
32
    #[source]
33
    detail: Box<ErrorDetail>,
34
}
35

            
36
impl From<ErrorDetail> for Error {
37
1
    fn from(detail: ErrorDetail) -> Error {
38
1
        Error {
39
1
            detail: detail.into(),
40
1
        }
41
1
    }
42
}
43

            
44
/// Declare an enum as `pub` if `error_details` is enabled, and as `pub(crate)` otherwise.
45
#[cfg(feature = "error_detail")]
46
macro_rules! pub_if_error_detail {
47
    {  $(#[$meta:meta])* enum $e:ident $tt:tt } => {
48
        $(#[$meta])* pub enum $e $tt
49
    }
50
}
51

            
52
/// Declare an enum as `pub` if `error_details` is enabled, and as `pub(crate)` otherwise.
53
#[cfg(not(feature = "error_detail"))]
54
macro_rules! pub_if_error_detail {
55
    {  $(#[$meta:meta])* enum $e:ident $tt:tt } => {
56
        $(#[$meta])* pub(crate) enum $e $tt }
57
}
58

            
59
// Hello, macro-fans!  There are some other solutions that we considered here
60
// but didn't use.
61
//
62
// 1. For one, `pub_if_error_detail!{} enum ErrorDetail { ... }` would be neat,
63
// but Rust doesn't allow macros to appear in that position.
64
//
65
// 2. We could also declare `ErrorDetail` here as `pub` unconditionally, and
66
// rely on `mod err` being private to keep it out of the user's hands.  Then we
67
// could conditionally re-export `ErrorDetail` in `lib`:
68
//
69
// ```
70
// mod err {
71
//    pub enum ErrorDetail { ... }
72
// }
73
//
74
// #[cfg(feature = "error_detail")]
75
// pub use err::ErrorDetail;
76
// ```
77
//
78
// But if we did that, the compiler would no longer warn us if we
79
// _unconditionally_ exposed the ErrorDetail type from somewhere else in this
80
// crate.  That doesn't seem too safe.
81
//
82
// 3. At one point we had a macro more like:
83
// ```
84
// macro_rules! declare_error_detail { { $vis: $vis } } =>
85
//  => { ... $vis enum ErrorDetail {...} }
86
// ```
87
// There's nothing wrong with that in principle, but it's no longer needed,
88
// since we used to use $vis in several places but now it's only used in one.
89
// Also, it's good to make macro declarations small, and rust-analyzer seems to
90
// handle understand format a little bit better.
91

            
92
pub_if_error_detail! {
93
// We cheat with the indentation, a bit.  Happily rustfmt doesn't seem to mind.
94

            
95
/// Represents errors that can occur while doing Tor operations.
96
///
97
/// This enumeration is the inner view of a
98
/// [`arti_client::Error`](crate::Error): we don't expose it unless the
99
/// `error_detail` feature is enabled.
100
///
101
/// The details of this enumeration are not stable: using the `error_detail`
102
/// feature will void your semver guarantee.
103
///
104
/// Instead of looking at the type, you try to should use the
105
/// [`kind`](`tor_error::HasKind::kind`) trait method to distinguish among
106
/// different kinds of [`Error`](crate::Error).  If that doesn't provide enough information
107
/// for your use case, please let us know.
108
#[derive(Error, Clone, Debug)]
109
#[non_exhaustive]
110
enum ErrorDetail {
111
    /// Error setting up the circuit manager
112
    #[error("Error setting up the circuit manager {0}")]
113
    CircMgrSetup(#[source] tor_circmgr::Error), // TODO should this be its own type?
114

            
115
    /// Failed to obtain exit circuit
116
    #[error("Failed to obtain exit circuit for {exit_ports}")]
117
    ObtainExitCircuit {
118
        /// What for
119
        exit_ports: TargetPorts,
120

            
121
        /// What went wrong
122
        #[source]
123
        cause: tor_circmgr::Error,
124
    },
125

            
126
    /// Error while getting a circuit
127
    #[error("Directory state error {0}")]
128
    DirMgr(#[from] tor_dirmgr::Error),
129

            
130
    /// A protocol error while launching a stream
131
    #[error("Protocol error while launching a stream: {0}")]
132
    Proto(#[from] tor_proto::Error),
133

            
134
    /// An error while interfacing with the persistent data layer.
135
    #[error("Error from state manager: {0}")]
136
    Persist(#[from] tor_persist::Error),
137

            
138
    /// We asked an exit to do something, and waited too long for an answer..
139
    #[error("exit timed out")]
140
    ExitTimeout,
141

            
142
    /// Onion services not supported.
143
    #[error("Rejecting .onion address as unsupported.")]
144
    OnionAddressNotSupported,
145

            
146
    /// Unusable target address.
147
    #[error("Could not parse target address: {0}")]
148
    Address(#[from] crate::address::TorAddrError),
149

            
150
    /// Hostname not valid.
151
    #[error("Rejecting hostname as invalid.")]
152
    InvalidHostname,
153

            
154
    /// Address was local, and that's not allowed.
155
    #[error("Cannot connect to a local-only address without enabling allow_local_addrs")]
156
    LocalAddress,
157

            
158
    /// Building configuration for the client failed.
159
    #[error("Configuration failed: {0}")]
160
    Configuration(#[from] tor_config::ConfigBuildError),
161

            
162
    /// Unable to change configuration.
163
    #[error("Reconfiguration failed: {0}")]
164
    Reconfigure(#[from] tor_config::ReconfigureError),
165

            
166
    /// Unable to spawn task
167
    #[error("unable to spawn {spawning}")]
168
    Spawn {
169
        /// What we were trying to spawn.
170
        spawning: &'static str,
171
        /// What happened when we tried to spawn it.
172
        #[source]
173
        cause: Arc<SpawnError>
174
    },
175

            
176
    /// Attempted to use an unbootstrapped `TorClient` for something that requires bootstrapping
177
    /// to have completed.
178
    #[error("cannot {action} with unbootstrapped client")]
179
    BootstrapRequired {
180
        /// What we were trying to do that required bootstrapping.
181
        action: &'static str
182
    },
183
}
184

            
185
// End of the use of $vis to refer to visibility according to `error_detail`
186
}
187

            
188
#[cfg(feature = "error_detail")]
189
impl Error {
190
    /// Return the underlying error detail object for this error.
191
    ///
192
    /// In general, it's not a good idea to use this function.  Our
193
    /// `arti_client::ErrorDetail` objects are unstable, and matching on them is
194
    /// probably not the best way to achieve whatever you're trying to do.
195
    /// Instead, we recommend using  the [`kind`](`tor_error::HasKind::kind`)
196
    /// trait method if your program needs to distinguish among different types
197
    /// of errors.
198
    ///
199
    /// (If the above function don't meet your needs, please let us know!)
200
    ///
201
    /// This function is only available when `arti-client` is built with the
202
    /// `error_detail` feature.  Using this function will void your semver
203
    /// guarantees.
204
    pub fn detail(&self) -> &ErrorDetail {
205
        &self.detail
206
    }
207
}
208

            
209
impl Error {
210
    /// Consume this error and return the underlying error detail object.
211
    pub(crate) fn into_detail(self) -> ErrorDetail {
212
        *self.detail
213
    }
214
}
215

            
216
impl ErrorDetail {
217
    /// Construct a new `Error` from a `SpawnError`.
218
    pub(crate) fn from_spawn(spawning: &'static str, err: SpawnError) -> ErrorDetail {
219
        ErrorDetail::Spawn {
220
            spawning,
221
            cause: Arc::new(err),
222
        }
223
    }
224
}
225

            
226
impl Display for Error {
227
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
228
        write!(f, "tor: {}: {}", self.detail.kind(), &self.detail)
229
    }
230
}
231

            
232
impl tor_error::HasKind for Error {
233
1
    fn kind(&self) -> ErrorKind {
234
1
        self.detail.kind()
235
1
    }
236
}
237

            
238
impl tor_error::HasKind for ErrorDetail {
239
1
    fn kind(&self) -> ErrorKind {
240
1
        use ErrorDetail as E;
241
1
        use ErrorKind as EK;
242
1
        match self {
243
            E::ObtainExitCircuit { cause, .. } => cause.kind(),
244
            E::ExitTimeout => EK::RemoteNetworkTimeout,
245
1
            E::BootstrapRequired { .. } => EK::BootstrapRequired,
246
            E::CircMgrSetup(e) => e.kind(),
247
            E::DirMgr(e) => e.kind(),
248
            E::Proto(e) => e.kind(),
249
            E::Persist(e) => e.kind(),
250
            E::Configuration(e) => e.kind(),
251
            E::Reconfigure(e) => e.kind(),
252
            E::Spawn { cause, .. } => cause.kind(),
253
            E::OnionAddressNotSupported => EK::NotImplemented,
254
            E::Address(_) | E::InvalidHostname => EK::InvalidStreamTarget,
255
            E::LocalAddress => EK::ForbiddenStreamTarget,
256
        }
257
1
    }
258
}
259

            
260
impl From<TorAddrError> for Error {
261
    fn from(e: TorAddrError) -> Error {
262
        e.into()
263
    }
264
}
265

            
266
#[cfg(test)]
267
mod test {
268
    use super::*;
269

            
270
    /// This code makes sure that our errors implement all the traits we want.
271
    #[test]
272
    fn traits_ok() {
273
        // I had intended to use `assert_impl`, but that crate can't check whether
274
        // a type is 'static.
275
        fn assert<
276
            T: Send + Sync + Clone + std::fmt::Debug + Display + std::error::Error + 'static,
277
        >() {
278
        }
279
        fn check() {
280
            assert::<Error>();
281
            assert::<ErrorDetail>();
282
        }
283
        check(); // doesn't do anything, but avoids "unused function" warnings.
284
    }
285
}