1use 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
9pub trait AsConflict {
12 fn is_conflict(&self) -> bool;
14
15 fn is_hard_conflict(&self) -> bool;
17
18 fn take_conflict(self) -> Option<ConflictError>;
20}
21
22#[derive(Debug, Error)]
24pub enum Error {
25 #[error("relay packet end of file")]
27 EndOfFile,
28
29 #[error(transparent)]
31 Conflict(#[from] ConflictError),
32
33 #[error(transparent)]
35 Io(#[from] std::io::Error),
36
37 #[error(transparent)]
39 TryFromSlice(#[from] std::array::TryFromSliceError),
40
41 #[error(transparent)]
43 ProtoBufEncode(#[from] prost::EncodeError),
44
45 #[error(transparent)]
47 ProtoBufDecode(#[from] prost::DecodeError),
48
49 #[error(transparent)]
51 ProtoEnum(#[from] prost::UnknownEnumValue),
52
53 #[error(transparent)]
55 Core(#[from] sos_core::Error),
56
57 #[error(transparent)]
59 Backend(#[from] sos_backend::Error),
60
61 #[error(transparent)]
63 BackendStorage(#[from] sos_backend::StorageError),
64
65 #[error(transparent)]
67 Signer(#[from] sos_signer::Error),
68
69 #[error(transparent)]
71 Sync(#[from] sos_sync::Error),
72
73 #[error(transparent)]
75 Merkle(#[from] rs_merkle::Error),
76
77 #[error(transparent)]
79 Time(#[from] time::error::ComponentRange),
80
81 #[error(transparent)]
83 UrlParse(#[from] url::ParseError),
84
85 #[error(transparent)]
87 Http(#[from] http::Error),
88
89 #[error(transparent)]
91 StatusCode(#[from] http::status::InvalidStatusCode),
92
93 #[error(transparent)]
95 Json(#[from] serde_json::Error),
96
97 #[error(transparent)]
99 Network(#[from] NetworkError),
100
101 #[error(transparent)]
103 Join(#[from] tokio::task::JoinError),
104
105 #[cfg(feature = "network-client")]
106 #[error(transparent)]
108 ToStr(#[from] reqwest::header::ToStrError),
109
110 #[cfg(feature = "network-client")]
111 #[error(transparent)]
113 Request(#[from] reqwest::Error),
114
115 #[cfg(feature = "network-client")]
116 #[error(transparent)]
118 Base58Decode(#[from] bs58::decode::Error),
119
120 #[cfg(feature = "network-client")]
121 #[error("file download checksum mismatch; expected '{0}' but got '{1}'")]
124 FileChecksumMismatch(String, String),
125
126 #[cfg(feature = "network-client")]
127 #[error("file transfer canceled")]
132 TransferCanceled(crate::transfer::CancelReason),
133
134 #[cfg(feature = "network-client")]
135 #[error("retry overflow")]
137 RetryOverflow,
138
139 #[cfg(feature = "network-client")]
140 #[error("network retry was canceled")]
142 RetryCanceled(crate::transfer::CancelReason),
143
144 #[cfg(feature = "listen")]
145 #[error("not binary message type on websocket")]
147 NotBinaryWebsocketMessageType,
148
149 #[cfg(feature = "listen")]
151 #[error(transparent)]
152 WebSocket(#[from] tokio_tungstenite::tungstenite::Error),
153}
154
155#[cfg(feature = "network-client")]
156impl Error {
157 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#[derive(Debug, Error)]
187pub enum NetworkError {
188 #[error("unexpected response status code {0}")]
190 ResponseCode(StatusCode),
191
192 #[error("unexpected response {1} (code: {0})")]
194 ResponseJson(StatusCode, Value),
195
196 #[error("unexpected content type {0}, expected: {1}")]
198 ContentType(String, String),
199}
200
201#[derive(Default, Serialize, Deserialize)]
203#[serde(default)]
204pub struct ErrorReply {
205 code: u16,
207 #[serde(skip_serializing_if = "Option::is_none")]
209 value: Option<Value>,
210 #[serde(skip_serializing_if = "Option::is_none")]
212 message: Option<String>,
213}
214
215impl ErrorReply {
216 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#[derive(Debug, Error)]
251pub enum ConflictError {
252 #[error("soft conflict")]
258 Soft {
259 conflict: MaybeConflict,
261 local: SyncStatus,
263 remote: SyncStatus,
265 },
266
267 #[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}