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

            
3
use thiserror::Error;
4

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

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

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

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

            
43
    /// An internal error.
44
    #[error("internal error")]
45
    Bug(#[from] tor_error::Bug),
46
}
47

            
48
impl From<tor_dirclient::RequestFailedError> for AuthorityRequestError {
49
    fn from(value: tor_dirclient::RequestFailedError) -> Self {
50
        Self::Request(Box::new(value))
51
    }
52
}
53

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

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

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

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

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

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

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

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

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

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