palpo_core/error/
mod.rs

1//! Errors that can be sent from the homeserver.
2
3use std::error::Error as StdError;
4use std::iter::FromIterator;
5use std::{fmt, time::Duration};
6
7use salvo::http::{Response, StatusCode, header};
8use salvo::writing::Scribe;
9use serde::{Deserialize, Serialize};
10use serde_json::{Map as JsonMap, Value as JsonValue, json};
11
12mod auth;
13pub use auth::*;
14mod kind;
15/// Deserialize and Serialize implementations for ErrorKind.
16/// Separate module because it's a lot of code.
17mod kind_serde;
18pub use kind::*;
19use crate::RoomVersionId;
20
21macro_rules! simple_kind_fns {
22    ($($fname:ident, $kind:ident;)+) => {
23        $(
24            /// Create a new `MatrixError`.
25            pub fn $fname(body: impl Into<ErrorBody>) -> Self {
26                Self::new(ErrorKind::$kind, body)
27            }
28        )+
29    }
30}
31#[derive(Deserialize, Serialize, Debug, Clone)]
32pub struct ErrorBody(JsonMap<String, JsonValue>);
33
34impl From<String> for ErrorBody {
35    fn from(message: String) -> Self {
36        Self(JsonMap::from_iter(vec![("error".to_owned(), json!(message))]))
37    }
38}
39impl From<&str> for ErrorBody {
40    fn from(message: &str) -> Self {
41        Self(JsonMap::from_iter(vec![("error".to_owned(), json!(message))]))
42    }
43}
44impl From<JsonMap<String, JsonValue>> for ErrorBody {
45    fn from(inner: JsonMap<String, JsonValue>) -> Self {
46        Self(inner)
47    }
48}
49
50/// A Matrix Error
51#[derive(Debug, Clone)]
52#[allow(clippy::exhaustive_structs)]
53pub struct MatrixError {
54    /// The http status code.
55    pub status_code: Option<http::StatusCode>,
56
57    /// The `WWW-Authenticate` header error message.
58    pub authenticate: Option<AuthenticateError>,
59    pub kind: ErrorKind,
60
61    /// The http response's body.
62    pub body: ErrorBody,
63}
64impl MatrixError {
65    pub fn new(kind: ErrorKind, body: impl Into<ErrorBody>) -> Self {
66        Self {
67            status_code: None,
68            authenticate: None,
69            kind,
70            body: body.into(),
71        }
72    }
73    simple_kind_fns! {
74        forbidden, Forbidden;
75        missing_token, MissingToken;
76        bad_json, BadJson;
77        not_json, NotJson;
78        not_found, NotFound;
79        unknown, Unknown;
80        unrecognized, Unrecognized;
81        unauthorized, Unauthorized;
82        user_deactivated, UserDeactivated;
83        user_in_use, UserInUse;
84        invalid_username, InvalidUsername;
85        room_in_use, RoomInUse;
86        invalid_room_state, InvalidRoomState;
87        threepid_in_use, ThreepidInUse;
88        threepid_not_found, ThreepidNotFound;
89        threepid_auth_failed, ThreepidAuthFailed;
90        threepid_denied, ThreepidDenied;
91        server_not_trusted, ServerNotTrusted;
92        unsupported_room_version, UnsupportedRoomVersion;
93        bad_state, BadState;
94        guest_access_forbidden, GuestAccessForbidden;
95        captcha_needed, CaptchaNeeded;
96        captcha_invalid, CaptchaInvalid;
97        missing_param, MissingParam;
98        invalid_param, InvalidParam;
99        too_large, TooLarge;
100        exclusive, Exclusive;
101        cannot_leave_server_notice_room, CannotLeaveServerNoticeRoom;
102        weak_password, WeakPassword;
103        unable_to_authorize_join, UnableToAuthorizeJoin;
104        unable_to_grant_join, UnableToGrantJoin;
105        bad_alias, BadAlias;
106        duplicate_annotation, DuplicateAnnotation;
107        not_yet_uploaded, NotYetUploaded;
108        cannot_overwrite_media, CannotOverwriteMedia;
109        unknown_pos, UnknownPos;
110        url_not_set, UrlNotSet;
111        bad_status, BadStatus;
112        connection_failed, ConnectionFailed;
113        connection_timeout, ConnectionTimeout;
114    }
115    pub fn unknown_token(soft_logout: bool, body: impl Into<ErrorBody>) -> Self {
116        Self::new(ErrorKind::UnknownToken { soft_logout }, body)
117    }
118    pub fn limit_exceeded(retry_after_ms: Option<Duration>, body: impl Into<ErrorBody>) -> Self {
119        Self::new(ErrorKind::LimitExceeded { retry_after_ms }, body)
120    }
121    pub fn incompatible_room_version(room_version: RoomVersionId, body: impl Into<ErrorBody>) -> Self {
122        Self::new(ErrorKind::IncompatibleRoomVersion { room_version }, body)
123    }
124    pub fn resource_limit_exceeded(admin_contact: String, body: impl Into<ErrorBody>) -> Self {
125        Self::new(ErrorKind::ResourceLimitExceeded { admin_contact }, body)
126    }
127    pub fn wrong_room_keys_version(current_version: Option<String>, body: impl Into<ErrorBody>) -> Self {
128        Self::new(ErrorKind::WrongRoomKeysVersion { current_version }, body)
129    }
130    pub fn is_not_found(&self) -> bool {
131        matches!(self.kind, ErrorKind::NotFound)
132    }
133}
134impl Serialize for MatrixError {
135    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
136    where
137        S: serde::Serializer,
138    {
139        self.body.serialize(serializer)
140    }
141}
142
143impl fmt::Display for MatrixError {
144    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
145        let code = self.status_code.unwrap_or(StatusCode::BAD_REQUEST).as_u16();
146        write!(f, "[{code} / {}]", self.kind)
147    }
148}
149
150impl StdError for MatrixError {}
151
152impl Scribe for MatrixError {
153    fn render(self, res: &mut Response) {
154        println!("MatrixError {}  {:?}", self.to_string(), self.body);
155        res.add_header(header::CONTENT_TYPE, "application/json", true).ok();
156
157        if res.status_code.map(|c| c.is_success()).unwrap_or(true) {
158            let code = self.status_code.unwrap_or_else(|| {
159                use ErrorKind::*;
160                match self.kind.clone() {
161                    Forbidden | GuestAccessForbidden | ThreepidAuthFailed | ThreepidDenied => StatusCode::FORBIDDEN,
162                    Unauthorized | UnknownToken { .. } | MissingToken => StatusCode::UNAUTHORIZED,
163                    NotFound | Unrecognized => StatusCode::NOT_FOUND,
164                    LimitExceeded { .. } => StatusCode::TOO_MANY_REQUESTS,
165                    UserDeactivated => StatusCode::FORBIDDEN,
166                    TooLarge => StatusCode::PAYLOAD_TOO_LARGE,
167                    CannotOverwriteMedia => StatusCode::CONFLICT,
168                    NotYetUploaded => StatusCode::GATEWAY_TIMEOUT,
169                    _ => StatusCode::BAD_REQUEST,
170                }
171            });
172            res.status_code(code);
173        }
174
175        if let Some(auth_error) = &self.authenticate {
176            res.add_header(header::WWW_AUTHENTICATE, auth_error, true).ok();
177        };
178
179        let Self { kind, mut body, .. } = self;
180        body.0.insert("errcode".to_owned(), kind.to_string().into());
181
182        let bytes: Vec<u8> = crate::serde::json_to_buf(&body.0).unwrap();
183        res.write_body(bytes).ok();
184    }
185}
186
187/// An error that happens when Palpo cannot understand a Matrix version.
188#[derive(Debug)]
189pub struct UnknownVersionError;
190
191impl fmt::Display for UnknownVersionError {
192    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
193        write!(f, "version string was unknown")
194    }
195}
196
197impl StdError for UnknownVersionError {}
198
199// #[cfg(test)]
200// mod tests {
201//     use assert_matches2::assert_matches;
202//     use serde_json::{from_value as from_json_value, json};
203
204//     use super::{ErrorKind, StandardErrorBody};
205
206//     #[test]
207//     fn deserialize_forbidden() {
208//         let deserialized: StandardErrorBody = from_json_value(json!({
209//             "errcode": "M_FORBIDDEN",
210//             "error": "You are not authorized to ban users in this room.",
211//         }))
212//         .unwrap();
213
214//         assert_eq!(deserialized.kind, ErrorKind::Forbidden);
215//         assert_eq!(
216//             deserialized.message,
217//             "You are not authorized to ban users in this room."
218//         );
219//     }
220
221//     #[test]
222//     fn deserialize_wrong_room_key_version() {
223//         let deserialized: StandardErrorBody = from_json_value(json!({
224//             "current_version": "42",
225//             "errcode": "M_WRONG_ROOM_KEYS_VERSION",
226//             "error": "Wrong backup version."
227//         }))
228//         .expect("We should be able to deserialize a wrong room keys version error");
229
230//         assert_matches!(deserialized.kind, ErrorKind::WrongRoomKeysVersion { current_version });
231//         assert_eq!(current_version.as_deref(), Some("42"));
232//         assert_eq!(deserialized.message, "Wrong backup version.");
233//     }
234
235//     #[test]
236//     fn custom_authenticate_error_sanity() {
237//         use super::AuthenticateError;
238
239//         let s = "Bearer error=\"custom_error\", misc=\"some content\"";
240
241//         let error = AuthenticateError::from_str(s).unwrap();
242//         let error_header = http::HeaderValue::try_from(&error).unwrap();
243
244//         assert_eq!(error_header.to_str().unwrap(), s);
245//     }
246
247//     #[test]
248//     fn serialize_insufficient_scope() {
249//         use super::AuthenticateError;
250
251//         let error = AuthenticateError::InsufficientScope {
252//             scope: "something_privileged".to_owned(),
253//         };
254//         let error_header = http::HeaderValue::try_from(&error).unwrap();
255
256//         assert_eq!(
257//             error_header.to_str().unwrap(),
258//             "Bearer error=\"insufficient_scope\", scope=\"something_privileged\""
259//         );
260//     }
261
262//     #[test]
263//     fn deserialize_insufficient_scope() {
264//         use super::{AuthenticateError, Error, ErrorBody};
265//         use crate::api::EndpointError;
266
267//         let response = http::Response::builder()
268//             .header(
269//                 http::header::WWW_AUTHENTICATE,
270//                 "Bearer error=\"insufficient_scope\", scope=\"something_privileged\"",
271//             )
272//             .status(http::StatusCode::UNAUTHORIZED)
273//             .body(
274//                 serde_json::to_string(&json!({
275//                     "errcode": "M_FORBIDDEN",
276//                     "error": "Insufficient privilege",
277//                 }))
278//                 .unwrap(),
279//             )
280//             .unwrap();
281//         let error = Error::from_http_response(response);
282
283//         assert_eq!(error.status_code, http::StatusCode::UNAUTHORIZED);
284//         assert_matches!(error.body, ErrorBody::Standard { kind, message });
285//         assert_eq!(kind, ErrorKind::Forbidden);
286//         assert_eq!(message, "Insufficient privilege");
287//         assert_matches!(error.authenticate, Some(AuthenticateError::InsufficientScope { scope }));
288//         assert_eq!(scope, "something_privileged");
289//     }
290// }