sos_protocol/
error.rs

1//! Error type for the wire protocol.
2use http::StatusCode;
3use serde::{Deserialize, Serialize};
4use serde_json::Value;
5use sos_sync::{MaybeConflict, SyncStatus};
6use std::error::Error as StdError;
7use thiserror::Error;
8
9/// Trait for error implementations that
10/// support a conflict error.
11pub trait AsConflict {
12    /// Determine if this is a conflict error.
13    fn is_conflict(&self) -> bool;
14
15    /// Determine if this is a hard conflict error.
16    fn is_hard_conflict(&self) -> bool;
17
18    /// Take an underlying conflict error.
19    fn take_conflict(self) -> Option<ConflictError>;
20}
21
22/// Errors generated by the wire protocol.
23#[derive(Debug, Error)]
24pub enum Error {
25    /// Reached EOF decoding a relay packet.
26    #[error("relay packet end of file")]
27    EndOfFile,
28
29    /// Error generated when a conflict is detected.
30    #[error(transparent)]
31    Conflict(#[from] ConflictError),
32
33    /// Error generated by the IO module.
34    #[error(transparent)]
35    Io(#[from] std::io::Error),
36
37    /// Error generated converting from a slice.
38    #[error(transparent)]
39    TryFromSlice(#[from] std::array::TryFromSliceError),
40
41    /// Error generated by the protobuf library when encoding.
42    #[error(transparent)]
43    ProtoBufEncode(#[from] prost::EncodeError),
44
45    /// Error generated by the protobuf library when decoding.
46    #[error(transparent)]
47    ProtoBufDecode(#[from] prost::DecodeError),
48
49    /// Error generated by the protobuf library when converting enums.
50    #[error(transparent)]
51    ProtoEnum(#[from] prost::UnknownEnumValue),
52
53    /// Error generated by the core library.
54    #[error(transparent)]
55    Core(#[from] sos_core::Error),
56
57    /// Error generated by the backend library.
58    #[error(transparent)]
59    Backend(#[from] sos_backend::Error),
60
61    /// Error generated by the backendcstorage.
62    #[error(transparent)]
63    BackendStorage(#[from] sos_backend::StorageError),
64
65    /// Error generated by the signer library.
66    #[error(transparent)]
67    Signer(#[from] sos_signer::Error),
68
69    /// Error generated by the sync library.
70    #[error(transparent)]
71    Sync(#[from] sos_sync::Error),
72
73    /// Error generated by the merkle tree library.
74    #[error(transparent)]
75    Merkle(#[from] rs_merkle::Error),
76
77    /// Error generated converting time types.
78    #[error(transparent)]
79    Time(#[from] time::error::ComponentRange),
80
81    /// Error generated parsing URLs.
82    #[error(transparent)]
83    UrlParse(#[from] url::ParseError),
84
85    /// Error generated by the HTTP library.
86    #[error(transparent)]
87    Http(#[from] http::Error),
88
89    /// Error generated by the HTTP library.
90    #[error(transparent)]
91    StatusCode(#[from] http::status::InvalidStatusCode),
92
93    /// Error generated by the JSON library.
94    #[error(transparent)]
95    Json(#[from] serde_json::Error),
96
97    /// Error generated by network communication.
98    #[error(transparent)]
99    Network(#[from] NetworkError),
100
101    /// Error generated joining a task.
102    #[error(transparent)]
103    Join(#[from] tokio::task::JoinError),
104
105    #[cfg(feature = "network-client")]
106    /// Error generated converting a header to a string.
107    #[error(transparent)]
108    ToStr(#[from] reqwest::header::ToStrError),
109
110    #[cfg(feature = "network-client")]
111    /// Error generated by the HTTP request library.
112    #[error(transparent)]
113    Request(#[from] reqwest::Error),
114
115    #[cfg(feature = "network-client")]
116    /// Error generated decoding a base58 string.
117    #[error(transparent)]
118    Base58Decode(#[from] bs58::decode::Error),
119
120    #[cfg(feature = "network-client")]
121    /// Error generated when a downloaded file checksum does not
122    /// match the expected checksum.
123    #[error("file download checksum mismatch; expected '{0}' but got '{1}'")]
124    FileChecksumMismatch(String, String),
125
126    #[cfg(feature = "network-client")]
127    /// Error generated when a file transfer is canceled.
128    ///
129    /// The boolean flag indicates whether the cancellation was
130    /// triggered by the user.
131    #[error("file transfer canceled")]
132    TransferCanceled(crate::transfer::CancelReason),
133
134    #[cfg(feature = "network-client")]
135    /// Overflow error calculating the retry exponential factor.
136    #[error("retry overflow")]
137    RetryOverflow,
138
139    #[cfg(feature = "network-client")]
140    /// Network retry was canceled possibly by the user.
141    #[error("network retry was canceled")]
142    RetryCanceled(crate::transfer::CancelReason),
143
144    #[cfg(feature = "listen")]
145    /// Error generated when a websocket message is not binary.
146    #[error("not binary message type on websocket")]
147    NotBinaryWebsocketMessageType,
148
149    /// Error generated by the websocket client.
150    #[cfg(feature = "listen")]
151    #[error(transparent)]
152    WebSocket(#[from] tokio_tungstenite::tungstenite::Error),
153}
154
155#[cfg(feature = "network-client")]
156impl Error {
157    /// Determine if this is a canceled error and
158    /// whether the cancellation was triggered by the user.
159    pub fn cancellation_reason(
160        &self,
161    ) -> Option<&crate::transfer::CancelReason> {
162        let source = source_error(self);
163        if let Some(err) = source.downcast_ref::<Error>() {
164            if let Error::TransferCanceled(reason) = err {
165                Some(reason)
166            } else {
167                None
168            }
169        } else {
170            None
171        }
172    }
173}
174
175pub(crate) fn source_error<'a>(
176    error: &'a (dyn StdError + 'static),
177) -> &'a (dyn StdError + 'static) {
178    let mut source = error;
179    while let Some(next_source) = source.source() {
180        source = next_source;
181    }
182    source
183}
184
185/// Error created communicating over the network.
186#[derive(Debug, Error)]
187pub enum NetworkError {
188    /// Error generated when an unexpected response code is received.
189    #[error("unexpected response status code {0}")]
190    ResponseCode(StatusCode),
191
192    /// Error generated when an unexpected response code is received.
193    #[error("unexpected response {1} (code: {0})")]
194    ResponseJson(StatusCode, Value),
195
196    /// Error generated when an unexpected content type is returend.
197    #[error("unexpected content type {0}, expected: {1}")]
198    ContentType(String, String),
199}
200
201/// Error reply.
202#[derive(Default, Serialize, Deserialize)]
203#[serde(default)]
204pub struct ErrorReply {
205    /// Status code.
206    code: u16,
207    /// Data value.
208    #[serde(skip_serializing_if = "Option::is_none")]
209    value: Option<Value>,
210    /// Error message.
211    #[serde(skip_serializing_if = "Option::is_none")]
212    message: Option<String>,
213}
214
215impl ErrorReply {
216    /// New error reply with a message.
217    pub fn new_message(
218        status: StatusCode,
219        message: impl std::fmt::Display,
220    ) -> Self {
221        Self {
222            code: status.into(),
223            message: Some(message.to_string()),
224            ..Default::default()
225        }
226    }
227}
228
229impl From<NetworkError> for ErrorReply {
230    fn from(value: NetworkError) -> Self {
231        match value {
232            NetworkError::ResponseCode(status) => ErrorReply {
233                code: status.into(),
234                ..Default::default()
235            },
236            NetworkError::ResponseJson(status, value) => ErrorReply {
237                code: status.into(),
238                value: Some(value),
239                ..Default::default()
240            },
241            NetworkError::ContentType(_, _) => ErrorReply {
242                code: StatusCode::BAD_REQUEST.into(),
243                ..Default::default()
244            },
245        }
246    }
247}
248
249/// Error created whan a conflict is detected.
250#[derive(Debug, Error)]
251pub enum ConflictError {
252    /// Error generated when a soft conflict was detected.
253    ///
254    /// A soft conflict may be resolved by searching for a
255    /// common ancestor commit and merging changes since
256    /// the common ancestor commit.
257    #[error("soft conflict")]
258    Soft {
259        /// Conflict information.
260        conflict: MaybeConflict,
261        /// Local information sent to the remote.
262        local: SyncStatus,
263        /// Remote information in the server reply.
264        remote: SyncStatus,
265    },
266
267    /// Error generated when a hard conflict was detected.
268    ///
269    /// A hard conflict is triggered after a soft conflict
270    /// attempted to scan proofs on a remote and was unable
271    /// to find a common ancestor commit.
272    #[error("hard conflict")]
273    Hard,
274}
275
276impl AsConflict for Error {
277    fn is_conflict(&self) -> bool {
278        matches!(self, Error::Conflict(_))
279    }
280
281    fn is_hard_conflict(&self) -> bool {
282        matches!(self, Error::Conflict(ConflictError::Hard))
283    }
284
285    fn take_conflict(self) -> Option<ConflictError> {
286        match self {
287            Self::Conflict(err) => Some(err),
288            _ => None,
289        }
290    }
291}