1
//! Declare tor client specific errors.
2

            
3
mod hint;
4

            
5
use std::fmt::{self, Display};
6
use std::sync::Arc;
7

            
8
use futures::task::SpawnError;
9

            
10
#[cfg(feature = "onion-service-client")]
11
use safelog::Redacted;
12
use safelog::Sensitive;
13
use thiserror::Error;
14
use tor_circmgr::TargetPorts;
15
use tor_error::{ErrorKind, HasKind};
16

            
17
use crate::TorAddrError;
18
#[cfg(feature = "onion-service-client")]
19
use tor_hscrypto::pk::HsId;
20

            
21
pub use hint::HintableError;
22

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

            
45
impl From<ErrorDetail> for Error {
46
2
    fn from(detail: ErrorDetail) -> Error {
47
2
        Error {
48
2
            detail: detail.into(),
49
2
        }
50
2
    }
51
}
52

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

            
61
/// Declare an enum as `pub` if `error_details` is enabled, and as `pub(crate)` otherwise.
62
#[cfg(not(feature = "error_detail"))]
63
macro_rules! pub_if_error_detail {
64
    {  $(#[$meta:meta])* enum $e:ident $tt:tt } => {
65
        $(#[$meta])* pub(crate) enum $e $tt }
66
}
67

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

            
101
pub_if_error_detail! {
102
// We cheat with the indentation, a bit.  Happily rustfmt doesn't seem to mind.
103

            
104
/// Represents errors that can occur while doing Tor operations.
105
///
106
/// This enumeration is the inner view of a
107
/// [`arti_client::Error`](crate::Error): we don't expose it unless the
108
/// `error_detail` feature is enabled.
109
///
110
/// The details of this enumeration are not stable: using the `error_detail`
111
/// feature will void your semver guarantee.
112
///
113
/// Instead of looking at the type, you should try to use the
114
/// [`kind`](`tor_error::HasKind::kind`) trait method to distinguish among
115
/// different kinds of [`Error`](struct@crate::Error).  If that doesn't provide enough information
116
/// for your use case, please let us know.
117
#[cfg_attr(docsrs, doc(cfg(feature = "error_detail")))]
118
#[cfg_attr(test, derive(strum::EnumDiscriminants))]
119
#[cfg_attr(test, strum_discriminants(vis(pub(crate))))]
120
#[derive(Error, Clone, Debug)]
121
#[non_exhaustive]
122
enum ErrorDetail {
123
    /// Error setting up the memory quota tracker
124
    #[error("Error setting up the memory quota tracker")]
125
    MemquotaSetup(#[from] tor_memquota::StartupError),
126

            
127
    /// Memory quota error while starting up Arti
128
    #[error("Memory quota error during startup")]
129
    MemquotaDuringStartup(#[from] tor_memquota::Error),
130

            
131
    /// Error setting up the channel manager
132
    // TODO: should "chanmgr setup error" be its own type in tor-chanmgr
133
    #[error("Error setting up the channel manager")]
134
    ChanMgrSetup(#[source] tor_chanmgr::Error),
135

            
136
    /// Error setting up the guard manager
137
    // TODO: should "guardmgr setup error" be its own type in tor-guardmgr?
138
    #[error("Error setting up the guard manager")]
139
    GuardMgrSetup(#[source] tor_guardmgr::GuardMgrError),
140

            
141
    /// Error setting up the guard manager
142
    // TODO: should "vanguardmgr setup error" be its own type in tor-guardmgr?
143
    #[cfg(all(
144
        feature = "vanguards",
145
        any(feature = "onion-service-client", feature = "onion-service-service")
146
    ))]
147
    #[error("Error setting up the vanguard manager")]
148
    VanguardMgrSetup(#[source] tor_guardmgr::VanguardMgrError),
149

            
150
    /// Error setting up the circuit manager
151
    // TODO: should "circmgr setup error" be its own type in tor-circmgr?
152
    #[error("Error setting up the circuit manager")]
153
    CircMgrSetup(#[source] tor_circmgr::Error),
154

            
155
    /// Error setting up the bridge descriptor manager
156
    #[error("Error setting up the bridge descriptor manager")]
157
    #[cfg(feature = "bridge-client")]
158
    BridgeDescMgrSetup(#[from] tor_dirmgr::bridgedesc::StartupError),
159

            
160
    /// Error setting up the directory manager
161
    // TODO: should "dirmgr setup error" be its own type in tor-dirmgr?
162
    #[error("Error setting up the directory manager")]
163
    DirMgrSetup(#[source] tor_dirmgr::Error),
164

            
165
    /// Error setting up the state manager.
166
    #[error("Error setting up the persistent state manager")]
167
    StateMgrSetup(#[source] tor_persist::Error),
168

            
169
    /// Error setting up the hidden service client connector.
170
    #[error("Error setting up the hidden service client connector")]
171
    #[cfg(feature = "onion-service-client")]
172
    HsClientConnectorSetup(#[from] tor_hsclient::StartupError),
173

            
174
    /// Error setting up onion service.
175
    #[cfg(feature= "onion-service-service")]
176
    #[error("Error setting up onion service")]
177
    OnionServiceSetup(#[source] tor_hsservice::StartupError),
178

            
179
    /// Failed to obtain exit circuit
180
    #[error("Failed to obtain exit circuit for ports {exit_ports}")]
181
    ObtainExitCircuit {
182
        /// The ports that we wanted a circuit for.
183
        exit_ports: Sensitive<TargetPorts>,
184

            
185
        /// What went wrong
186
        #[source]
187
        cause: tor_circmgr::Error,
188
    },
189

            
190
    /// Failed to obtain hidden service circuit
191
    #[cfg(feature = "onion-service-client")]
192
    #[error("Failed to obtain hidden service circuit to {hsid}")]
193
    ObtainHsCircuit {
194
        /// The service we were trying to connect to
195
        hsid: Redacted<HsId>,
196

            
197
        /// What went wrong
198
        #[source]
199
        cause: tor_hsclient::ConnError,
200
    },
201

            
202
    /// Directory manager was unable to bootstrap a working directory.
203
    #[error("Unable to bootstrap a working directory")]
204
    DirMgrBootstrap(#[source] tor_dirmgr::Error),
205

            
206
    /// A protocol error while launching a stream
207
    #[error("Protocol error while launching a {kind} stream")]
208
    StreamFailed {
209
        /// What kind of stream we were trying to launch.
210
        kind: &'static str,
211

            
212
        /// The error that occurred.
213
        #[source]
214
        cause:  tor_proto::Error
215
    },
216

            
217
    /// An error while interfacing with the persistent data layer.
218
    #[error("Error while trying to access persistent state")]
219
    StateAccess(#[source] tor_persist::Error),
220

            
221
    /// We asked an exit to do something, and waited too long for an answer.
222
    #[error("Timed out while waiting for answer from exit")]
223
    ExitTimeout,
224

            
225
    /// Onion services are not compiled in, but we were asked to connect to one.
226
    #[error("Rejecting .onion address; feature onion-service-client not compiled in")]
227
    OnionAddressNotSupported,
228

            
229
    /// Onion services are not enabled, but we were asked to connect to one.
230
    ///
231
    /// This error occurs when Arti is built with onion service support, but
232
    /// onion services are disabled via our stream preferences.
233
    ///
234
    /// To enable onion services, set `allow_onion_addrs` to `true` in the
235
    /// `address_filter` configuration section.  Alternatively, set
236
    /// `connect_to_onion_services` in your `StreamPrefs` object.
237
    #[cfg(feature = "onion-service-client")]
238
    #[error("Rejecting .onion address; allow_onion_addrs disabled in stream preferences")]
239
    OnionAddressDisabled,
240

            
241
    /// Error when trying to find the IP address of a hidden service
242
    #[error("A .onion address cannot be resolved to an IP address")]
243
    OnionAddressResolveRequest,
244

            
245
    /// Unusable target address.
246
    ///
247
    /// `TorAddrError::InvalidHostname` should not appear here;
248
    /// use `ErrorDetail::InvalidHostname` instead.
249
    // TODO this is a violation of the "make invalid states unrepresentable" principle,
250
    // but maybe that doesn't matter too much here?
251
    #[error("Could not parse target address")]
252
    Address(crate::address::TorAddrError),
253

            
254
    /// Hostname not valid.
255
    #[error("Rejecting hostname as invalid")]
256
    InvalidHostname,
257

            
258
    /// Address was local, and we don't permit connecting to those over Tor.
259
    #[error("Cannot connect to a local-only address without enabling allow_local_addrs")]
260
    LocalAddress,
261

            
262
    /// Building configuration for the client failed.
263
    #[error("Problem with configuration")]
264
    Configuration(#[from] tor_config::ConfigBuildError),
265

            
266
    /// Unable to change configuration.
267
    #[error("Unable to change configuration")]
268
    Reconfigure(#[from] tor_config::ReconfigureError),
269

            
270
    /// Problem creating or launching a pluggable transport.
271
    #[cfg(feature="pt-client")]
272
    #[error("Problem with a pluggable transport")]
273
    PluggableTransport(#[from] tor_ptmgr::err::PtError),
274

            
275
    /// We encountered a problem while inspecting or creating a directory.
276
    #[error("Problem accessing filesystem")]
277
    FsMistrust(#[from] fs_mistrust::Error),
278

            
279
    /// Unable to spawn task
280
    #[error("Unable to spawn {spawning}")]
281
    Spawn {
282
        /// What we were trying to spawn.
283
        spawning: &'static str,
284
        /// What happened when we tried to spawn it.
285
        #[source]
286
        cause: Arc<SpawnError>
287
    },
288

            
289
    /// Attempted to use an unbootstrapped `TorClient` for something that
290
    /// requires bootstrapping to have completed.
291
    #[error("Cannot {action} with unbootstrapped client")]
292
    BootstrapRequired {
293
        /// What we were trying to do that required bootstrapping.
294
        action: &'static str
295
    },
296

            
297
    /// Attempted to use a `TorClient` for something when it did not
298
    /// have a valid directory.
299
    #[error("Tried to {action} without a valid directory")]
300
    NoDir {
301
        /// The underlying error.
302
        #[source]
303
        error: tor_netdir::Error,
304
        /// What we were trying to do that needed a directory.
305
        action: &'static str,
306
    },
307

            
308
    /// A key store access failed.
309
    #[error("Error while trying to access a key store")]
310
    Keystore(#[from] tor_keymgr::Error),
311

            
312
    /// Attempted to use a `TorClient` for something that
313
    /// requires the keystore to be enabled in the configuration.
314
    #[error("Cannot {action} without enabling storage.keystore")]
315
    KeystoreRequired {
316
        /// What we were trying to do that required the keystore to be enabled.
317
        action: &'static str
318
    },
319

            
320
    /// Encountered a malformed client specifier.
321
    #[error("Bad client specifier")]
322
    BadClientSpecifier(#[from] tor_keymgr::ArtiPathSyntaxError),
323

            
324
    /// We tried to parse an onion address, but we found that it was invalid.
325
    ///
326
    /// This error occurs if we are asked to connect to an invalid .onion address.
327
    #[cfg(feature = "onion-service-client")]
328
    #[error("Invalid onion address")]
329
    BadOnionAddress(#[from] tor_hscrypto::pk::HsIdParseError),
330

            
331
    /// We were unable to launch an onion service, even though we
332
    /// we are configured to be able to do so.
333
    #[cfg(feature= "onion-service-service")]
334
    #[error("Unable to launch onion service")]
335
    LaunchOnionService(#[source] tor_hsservice::StartupError),
336

            
337
    /// We found that at least one required protocol was missing.
338
    #[error("Arti is missing a required protocol feature")]
339
    MissingProtocol(#[source] tor_netdoc::doc::netstatus::ProtocolSupportError),
340

            
341
    /// A programming problem, either in our code or the code calling it.
342
    #[error("Programming problem")]
343
    Bug(#[from] tor_error::Bug),
344
}
345

            
346
// End of the use of $vis to refer to visibility according to `error_detail`
347
}
348

            
349
#[cfg(feature = "error_detail")]
350
impl Error {
351
    /// Return the underlying error detail object for this error.
352
    ///
353
    /// In general, it's not a good idea to use this function.  Our
354
    /// `arti_client::ErrorDetail` objects are unstable, and matching on them is
355
    /// probably not the best way to achieve whatever you're trying to do.
356
    /// Instead, we recommend using  the [`kind`](`tor_error::HasKind::kind`)
357
    /// trait method if your program needs to distinguish among different types
358
    /// of errors.
359
    ///
360
    /// (If the above function don't meet your needs, please let us know!)
361
    ///
362
    /// This function is only available when `arti-client` is built with the
363
    /// `error_detail` feature.  Using this function will void your semver
364
    /// guarantees.
365
    pub fn detail(&self) -> &ErrorDetail {
366
        &self.detail
367
    }
368
}
369

            
370
impl Error {
371
    /// Consume this error and return the underlying error detail object.
372
    pub(crate) fn into_detail(self) -> ErrorDetail {
373
        *self.detail
374
    }
375
}
376

            
377
impl ErrorDetail {
378
    /// Construct a new `Error` from a `SpawnError`.
379
    pub(crate) fn from_spawn(spawning: &'static str, err: SpawnError) -> ErrorDetail {
380
        ErrorDetail::Spawn {
381
            spawning,
382
            cause: Arc::new(err),
383
        }
384
    }
385
}
386

            
387
impl Display for Error {
388
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
389
        write!(f, "tor: {}: {}", self.detail.kind(), &self.detail)
390
    }
391
}
392

            
393
impl tor_error::HasKind for Error {
394
2
    fn kind(&self) -> ErrorKind {
395
2
        self.detail.kind()
396
2
    }
397
}
398

            
399
impl tor_error::HasKind for ErrorDetail {
400
12
    fn kind(&self) -> ErrorKind {
401
        use ErrorDetail as E;
402
        use ErrorKind as EK;
403
12
        match self {
404
            E::ObtainExitCircuit { cause, .. } => cause.kind(),
405
            #[cfg(feature = "onion-service-client")]
406
            E::ObtainHsCircuit { cause, .. } => cause.kind(),
407
            E::ExitTimeout => EK::RemoteNetworkTimeout,
408
2
            E::BootstrapRequired { .. } => EK::BootstrapRequired,
409
            E::MemquotaSetup(e) => e.kind(),
410
            E::MemquotaDuringStartup(e) => e.kind(),
411
            E::GuardMgrSetup(e) => e.kind(),
412
            #[cfg(all(
413
                feature = "vanguards",
414
                any(feature = "onion-service-client", feature = "onion-service-service")
415
            ))]
416
            E::VanguardMgrSetup(e) => e.kind(),
417
            #[cfg(feature = "bridge-client")]
418
            E::BridgeDescMgrSetup(e) => e.kind(),
419
            E::CircMgrSetup(e) => e.kind(),
420
            E::DirMgrSetup(e) => e.kind(),
421
            E::StateMgrSetup(e) => e.kind(),
422
            #[cfg(feature = "onion-service-client")]
423
            E::HsClientConnectorSetup(e) => e.kind(),
424
            #[cfg(feature = "onion-service-service")]
425
            E::OnionServiceSetup(e) => e.kind(),
426
            E::DirMgrBootstrap(e) => e.kind(),
427
            #[cfg(feature = "pt-client")]
428
            E::PluggableTransport(e) => e.kind(),
429
            E::StreamFailed { cause, .. } => cause.kind(),
430
            E::StateAccess(e) => e.kind(),
431
            E::Configuration(e) => e.kind(),
432
            E::Reconfigure(e) => e.kind(),
433
            E::Spawn { cause, .. } => cause.kind(),
434
            E::OnionAddressNotSupported => EK::FeatureDisabled,
435
8
            E::OnionAddressResolveRequest => EK::NotImplemented,
436
            #[cfg(feature = "onion-service-client")]
437
2
            E::OnionAddressDisabled => EK::ForbiddenStreamTarget,
438
            #[cfg(feature = "onion-service-client")]
439
            E::BadOnionAddress(_) => EK::InvalidStreamTarget,
440
            #[cfg(feature = "onion-service-service")]
441
            E::LaunchOnionService(e) => e.kind(),
442
            E::Address(e) => e.kind(),
443
            E::InvalidHostname => EK::InvalidStreamTarget,
444
            E::LocalAddress => EK::ForbiddenStreamTarget,
445
            E::ChanMgrSetup(e) => e.kind(),
446
            E::NoDir { error, .. } => error.kind(),
447
            E::Keystore(e) => e.kind(),
448
            E::KeystoreRequired { .. } => EK::InvalidConfig,
449
            E::BadClientSpecifier(_) => EK::InvalidConfig,
450
            E::FsMistrust(_) => EK::FsPermissions,
451
            E::MissingProtocol(_) => EK::SoftwareDeprecated,
452
            E::Bug(e) => e.kind(),
453
        }
454
12
    }
455
}
456

            
457
impl From<TorAddrError> for Error {
458
    fn from(e: TorAddrError) -> Error {
459
        ErrorDetail::from(e).into()
460
    }
461
}
462

            
463
impl From<tor_keymgr::Error> for Error {
464
    fn from(e: tor_keymgr::Error) -> Error {
465
        ErrorDetail::Keystore(e).into()
466
    }
467
}
468

            
469
impl From<TorAddrError> for ErrorDetail {
470
4
    fn from(e: TorAddrError) -> ErrorDetail {
471
        use ErrorDetail as E;
472
        use TorAddrError as TAE;
473
4
        match e {
474
2
            TAE::InvalidHostname => E::InvalidHostname,
475
2
            TAE::NoPort | TAE::BadPort => E::Address(e),
476
        }
477
4
    }
478
}
479

            
480
/// Verbose information about an error, meant to provide detail or justification
481
/// for user-facing errors, rather than the normal short message for
482
/// developer-facing errors.
483
///
484
/// User-facing code may attempt to produce this by calling [`Error::hint`].
485
/// Not all errors may wish to provide verbose messages. `Some(ErrorHint)` will be
486
/// returned if hinting is supported for the error. Err(()) will be returned otherwise.
487
/// Which errors support hinting, and the hint content, have no SemVer warranty and may
488
/// change in patch versions without warning. Callers should handle both cases,
489
/// falling back on the original error message in case of Err.
490
///
491
/// Since the internal machinery for constructing and displaying hints may change over time,
492
/// no data members are currently exposed. In the future we may wish to offer an unstable
493
/// API locked behind a feature, like we do with ErrorDetail.
494
#[derive(Clone, Debug)]
495
pub struct ErrorHint<'a> {
496
    /// The pieces of the message to display to the user
497
    inner: ErrorHintInner<'a>,
498
}
499

            
500
/// An inner enumeration, describing different kinds of error hint that we know how to give.
501
#[derive(Clone, Debug)]
502
enum ErrorHintInner<'a> {
503
    /// There is a misconfigured filesystem permission, reported by `fs-mistrust`.
504
    ///
505
    /// Tell the user to make their file more private, or to disable `fs-mistrust`.
506
    BadPermission {
507
        /// The location of the file.
508
        filename: &'a std::path::Path,
509
        /// The access bits set on the file.
510
        bits: u32,
511
        /// The access bits that, according to fs-mistrust, should not be set.
512
        badbits: u32,
513
    },
514

            
515
    /// At least one required protocol was missing.
516
    MissingProtocols {
517
        /// The list of missing required protocols
518
        required: &'a tor_protover::Protocols,
519
    },
520
}
521

            
522
// TODO: Perhaps we want to lower this logic to fs_mistrust crate, and have a
523
// separate `ErrorHint` type for each crate that can originate a hint.  But I'd
524
// rather _not_ have that turn into something that forces us to give a Hint for
525
// every intermediate crate.
526
impl<'a> Display for ErrorHint<'a> {
527
8
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
528
        use fs_mistrust::anon_home::PathExt as _;
529

            
530
8
        match self.inner {
531
            ErrorHintInner::BadPermission {
532
8
                filename,
533
8
                bits,
534
8
                badbits,
535
8
            } => {
536
8
                writeln!(
537
8
                    f,
538
8
                    "Permissions are set too permissively on {}: currently {}",
539
8
                    filename.anonymize_home(),
540
8
                    fs_mistrust::format_access_bits(bits, '=')
541
8
                )?;
542
8
                if 0 != badbits & 0o222 {
543
8
                    writeln!(
544
8
                        f,
545
8
                        "* Untrusted users could modify its contents and override our behavior.",
546
8
                    )?;
547
                }
548
8
                if 0 != badbits & 0o444 {
549
                    writeln!(f, "* Untrusted users could read its contents.")?;
550
8
                }
551
8
                writeln!(f,
552
8
                    "You can fix this by further restricting the permissions of your filesystem, using:\n\
553
8
                         chmod {} {}",
554
8
                        fs_mistrust::format_access_bits(badbits, '-'),
555
8
                        filename.anonymize_home())?;
556
8
                writeln!(f, "You can suppress this message by setting storage.permissions.dangerously_trust_everyone=true,\n\
557
8
                    or setting ARTI_FS_DISABLE_PERMISSION_CHECKS=yes in your environment.")?;
558
            }
559
            ErrorHintInner::MissingProtocols { required } => {
560
                writeln!(f, "The consensus directory says that we need to support certain protocols which we do not implement.")?;
561
                writeln!(f, "The missing protocols are: {}", required)?;
562
                writeln!(
563
                    f,
564
"The best solution is to upgrade to a more recent version of Arti.  If this is not possible,
565
you can list the missing protocols in the configuration option 'use_obsolete_software.ignore_missing_required_protocols'"
566
                )?;
567
            }
568
        }
569
8
        Ok(())
570
8
    }
571
}
572

            
573
impl Error {
574
    /// Return a hint object explaining how to solve this error, if we have one.
575
    ///
576
    /// Most errors won't have obvious hints, but some do.  For the ones that
577
    /// do, we can return an [`ErrorHint`].
578
    ///
579
    /// Right now, `ErrorHint` is completely opaque: the only supported option
580
    /// is to format it for human consumption.
581
4
    pub fn hint(&self) -> Option<ErrorHint> {
582
4
        HintableError::hint(self)
583
4
    }
584
}
585

            
586
#[cfg(test)]
587
mod test {
588
    // @@ begin test lint list maintained by maint/add_warning @@
589
    #![allow(clippy::bool_assert_comparison)]
590
    #![allow(clippy::clone_on_copy)]
591
    #![allow(clippy::dbg_macro)]
592
    #![allow(clippy::mixed_attributes_style)]
593
    #![allow(clippy::print_stderr)]
594
    #![allow(clippy::print_stdout)]
595
    #![allow(clippy::single_char_pattern)]
596
    #![allow(clippy::unwrap_used)]
597
    #![allow(clippy::unchecked_duration_subtraction)]
598
    #![allow(clippy::useless_vec)]
599
    #![allow(clippy::needless_pass_by_value)]
600
    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
601
    use super::*;
602

            
603
    /// This code makes sure that our errors implement all the traits we want.
604
    #[test]
605
    fn traits_ok() {
606
        // I had intended to use `assert_impl`, but that crate can't check whether
607
        // a type is 'static.
608
        fn assert<
609
            T: Send + Sync + Clone + std::fmt::Debug + Display + std::error::Error + 'static,
610
        >() {
611
        }
612
        fn check() {
613
            assert::<Error>();
614
            assert::<ErrorDetail>();
615
        }
616
        check(); // doesn't do anything, but avoids "unused function" warnings.
617
    }
618
}