1
//! Error module for `tor-dirserver`.
2

            
3
use thiserror::Error;
4
use tor_netdoc::parse2;
5

            
6
/// Indicates that an error variant is fatal.
7
///
8
/// A fatal error means that the application should abort execution and may
9
/// not retry again in the future.
10
///
11
/// This trait should only be implemented for error variants where some error
12
/// variants are fatal and others are not.  In other words: An error where all
13
/// variants are either fatal or non-fatal does not qualify for this trait.
14
// TODO DIRMIRROR: Move this to tor_error.
15
pub(crate) trait IsFatal: std::error::Error {
16
    /// Checks whether the current error is considered to be fatal.
17
    fn is_fatal(&self) -> bool;
18
}
19

            
20
/// An error while performing a request at a directory authority.
21
#[derive(Debug, Error)]
22
#[non_exhaustive]
23
pub(crate) enum AuthorityRequestError {
24
    /// TCP connection to the endpoint failed.
25
    #[error("tcp connection error: {0}")]
26
    TcpConnect(std::io::Error),
27

            
28
    /// [`tor_dirclient`] failed at performing the request.
29
    ///
30
    /// This usually indicates some failures in HTTP/1.0, such as the response
31
    /// not being valid HTTP/1.0; although Tor generally has some specialities
32
    /// here and there with regard to this, hence why we have dirclient in the
33
    /// first place.
34
    #[error("dirclient error: {0}")]
35
    Request(#[from] tor_dirclient::RequestFailedError),
36

            
37
    /// A response does not make semantic sense.
38
    ///
39
    /// This for example may include cases where we got more netdocs than we
40
    /// requested for.
41
    #[error("response error: {0}")]
42
    Response(&'static str),
43

            
44
    /// Invalid netdoc received from the authority.
45
    #[error("netdoc parse error: {0}")]
46
    Parse(#[from] parse2::ParseError),
47

            
48
    /// An internal error.
49
    #[error("internal error")]
50
    Bug(#[from] tor_error::Bug),
51
}
52

            
53
impl IsFatal for AuthorityRequestError {
54
    /// The [`AuthorityRequestError`] is considered to be fatal.
55
    ///
56
    /// Right now, the following variants are considered to be fatal:
57
    /// * [`AuthorityRequestError::Bug`]
58
    fn is_fatal(&self) -> bool {
59
        matches!(&self, Self::Bug(_))
60
    }
61
}
62

            
63
/// An error while interacting with a database.
64
///
65
/// This error should be returned by all functions that interact with the
66
/// database in one way or another.
67
#[derive(Debug, Error)]
68
#[non_exhaustive]
69
pub(crate) enum DatabaseError {
70
    /// A low-level SQLite error has occurred, which can have a basically
71
    /// infinite amount of reasons, all of them outlined in the actual SQLite
72
    /// and [`rusqlite`] documentation.
73
    #[error("low-level rusqlite error: {0}")]
74
    LowLevel(#[from] rusqlite::Error),
75

            
76
    /// This is an application level error meaning that the database can be
77
    /// successfully accessed but its content implies it is of a schema version
78
    /// we do not support.
79
    ///
80
    /// Keep in mind that an unrecognized schema is not equal to no schema.
81
    /// In the latter case we actually initialize the database, whereas in the
82
    /// previous one, we fail early in order to not corrupt an existing database.
83
    /// Future versions of this crate should continue with this promise in order
84
    /// to ensure forward compatibility.
85
    #[error("incompatible schema version: {version}")]
86
    IncompatibleSchema {
87
        /// The incompatible schema version found in the database.
88
        version: String,
89
    },
90

            
91
    /// Interaction with our database pool, [`r2d2`], has failed.
92
    ///
93
    /// Unlike other database pools, this error is fairly straightforward and
94
    /// may only be obtained in the cases in which we try to obtain a connection
95
    /// handle from the pool.  Notably, it does not fail if, for example,
96
    /// the low-level [`rusqlite`] has a failure.
97
    #[error("pool error: {0}")]
98
    Pool(#[from] r2d2::Error),
99

            
100
    /// An internal error.
101
    #[error("Internal error")]
102
    Bug(#[from] tor_error::Bug),
103
}
104

            
105
/// An error related to an operation in the dirmirror FSM.
106
//
107
// TODO: Rename this to MirrorOperationError.
108
#[derive(Debug, Error)]
109
#[non_exhaustive]
110
pub(crate) enum OperationError {
111
    /// Request to a directory authority failed.
112
    #[error("authority request error: {0}")]
113
    AuthorityRequest(#[from] Box<AuthorityRequestError>),
114

            
115
    /// Access to the database failed for good.
116
    #[error("database error: {0}")]
117
    Database(#[from] DatabaseError),
118

            
119
    /// An internal error.
120
    #[error("Internal error")]
121
    Bug(#[from] tor_error::Bug),
122
}
123

            
124
impl From<AuthorityRequestError> for OperationError {
125
    fn from(value: AuthorityRequestError) -> Self {
126
        Self::AuthorityRequest(Box::new(value))
127
    }
128
}
129

            
130
impl IsFatal for OperationError {
131
    /// The [`OperationError`] is considered to be fatal.
132
    ///
133
    /// Right now, the following variants are considered to be fatal:
134
    /// * [`OperationError::Database`]
135
    /// * [`OperationError::Bug`]
136
    fn is_fatal(&self) -> bool {
137
        matches!(&self, Self::Database(_) | Self::Bug(_))
138
    }
139
}