Skip to main content

mail4agent_server/
error.rs

1//! Matrix Client-Server error envelope. `status` is the HTTP status.
2//! [`IntoResponse`] writes it. The body is the serde JSON. `status` itself
3//! is not serialized.
4
5use axum::http::StatusCode;
6use axum::response::{IntoResponse, Response};
7use axum::Json;
8use serde::Serialize;
9
10use crate::keys::MatrixKeysStoreError;
11use crate::store::MatrixStoreError;
12
13#[derive(Debug, Clone, PartialEq, Serialize)]
14pub struct MatrixError {
15    #[serde(skip)]
16    pub status: u16,
17    pub errcode: &'static str,
18    pub error: String,
19    #[serde(skip_serializing_if = "Option::is_none")]
20    pub retry_after_ms: Option<u64>,
21    #[serde(skip_serializing_if = "Option::is_none")]
22    pub soft_logout: Option<bool>,
23    #[serde(skip_serializing_if = "Option::is_none")]
24    pub current_version: Option<String>,
25}
26
27impl MatrixError {
28    fn new(status: u16, errcode: &'static str, error: impl Into<String>) -> Self {
29        Self {
30            status,
31            errcode,
32            error: error.into(),
33            retry_after_ms: None,
34            soft_logout: None,
35            current_version: None,
36        }
37    }
38
39    pub fn missing_token() -> Self {
40        Self::new(401, "M_MISSING_TOKEN", "Missing access token")
41    }
42
43    pub fn unknown_token() -> Self {
44        Self {
45            soft_logout: Some(false),
46            ..Self::new(401, "M_UNKNOWN_TOKEN", "Unrecognised access token")
47        }
48    }
49
50    /// Federation request authentication failure (`M_UNAUTHORIZED`, 401).
51    pub fn unauthorized(msg: impl Into<String>) -> Self {
52        Self::new(401, "M_UNAUTHORIZED", msg)
53    }
54
55    /// Nick fails the grammar or a forbidden list (`M4A_INVALID_NICK`, 400).
56    pub fn invalid_nick(msg: impl Into<String>) -> Self {
57        Self::new(400, "M4A_INVALID_NICK", msg)
58    }
59
60    /// Nick equals another live nick, a frozen localpart or a reserved one (`M4A_NICK_TAKEN`, 409).
61    pub fn nick_taken() -> Self {
62        Self::new(409, "M4A_NICK_TAKEN", "nick is taken")
63    }
64
65    /// An issuer asserted a nick that collides with another account's localpart
66    /// (`M4A_NICK_CONFLICT`, 409). The issuer must pick another nick.
67    pub fn nick_conflict() -> Self {
68        Self::new(409, "M4A_NICK_CONFLICT", "nick conflicts with an existing account; choose another nick")
69    }
70
71    /// Nick changed too recently (`M4A_NICK_COOLDOWN`, 429).
72    pub fn nick_cooldown(retry_after_ms: u64) -> Self {
73        Self { retry_after_ms: Some(retry_after_ms), ..Self::new(429, "M4A_NICK_COOLDOWN", "nick was changed recently") }
74    }
75
76    /// The deployment's policy hook refused the action (`M4A_POLICY_DENIED`, 403).
77    pub fn policy_denied(msg: impl Into<String>) -> Self {
78        Self::new(403, "M4A_POLICY_DENIED", msg)
79    }
80
81    pub fn forbidden(msg: impl Into<String>) -> Self {
82        Self::new(403, "M_FORBIDDEN", msg)
83    }
84
85    pub fn not_found(msg: impl Into<String>) -> Self {
86        Self::new(404, "M_NOT_FOUND", msg)
87    }
88
89    pub fn unrecognized() -> Self {
90        Self::new(404, "M_UNRECOGNIZED", "Unrecognized request")
91    }
92
93    pub fn bad_json(msg: impl Into<String>) -> Self {
94        Self::new(400, "M_BAD_JSON", msg)
95    }
96
97    pub fn managed_account_data_type(msg: impl Into<String>) -> Self {
98        Self::new(405, "M_BAD_JSON", msg)
99    }
100
101    pub fn invalid_param(msg: impl Into<String>) -> Self {
102        Self::new(400, "M_INVALID_PARAM", msg)
103    }
104
105    pub fn duplicate_annotation() -> Self {
106        Self::new(400, "M_DUPLICATE_ANNOTATION", "Already reacted with this key")
107    }
108
109    pub fn wrong_room_keys_version(current_version: Option<i64>) -> Self {
110        Self {
111            current_version: current_version.map(|v| v.to_string()),
112            ..Self::new(403, "M_WRONG_ROOM_KEYS_VERSION", "Wrong backup version")
113        }
114    }
115
116    pub fn limit_exceeded(retry_after_ms: u64) -> Self {
117        Self {
118            retry_after_ms: Some(retry_after_ms),
119            ..Self::new(429, "M_LIMIT_EXCEEDED", "Too many requests")
120        }
121    }
122
123    pub fn unknown(msg: impl Into<String>) -> Self {
124        Self::new(500, "M_UNKNOWN", msg)
125    }
126
127    pub fn internal() -> Self {
128        Self::unknown("Internal error")
129    }
130}
131
132impl From<rusqlite::Error> for MatrixError {
133    fn from(e: rusqlite::Error) -> Self {
134        tracing::error!("messenger db error: {e}");
135        Self::internal()
136    }
137}
138
139impl From<MatrixStoreError> for MatrixError {
140    fn from(e: MatrixStoreError) -> Self {
141        match e {
142            MatrixStoreError::Db(e) => MatrixError::from(e),
143            MatrixStoreError::Json(e) => {
144                tracing::error!("messenger store JSON error: {e}");
145                MatrixError::bad_json("Malformed event content")
146            }
147            MatrixStoreError::ReservedLocalpart => MatrixError::forbidden("Reserved localpart"),
148            MatrixStoreError::DuplicateAnnotation => MatrixError::duplicate_annotation(),
149            MatrixStoreError::UnknownEventId(id) => MatrixError::not_found(format!("Unknown event: {id}")),
150            MatrixStoreError::UnknownMxid(id) => MatrixError::not_found(format!("Unknown user: {id}")),
151            MatrixStoreError::InvalidMembership(m) => MatrixError::invalid_param(format!("Invalid membership: {m}")),
152            MatrixStoreError::InvalidRelationTarget(id) => {
153                MatrixError::invalid_param(format!("Invalid relation target: {id}"))
154            }
155            MatrixStoreError::WrongRoom(id) => MatrixError::invalid_param(format!("Event is not in this room: {id}")),
156            MatrixStoreError::UnredactableEvent(t) => {
157                MatrixError::forbidden(format!("This event type cannot be redacted: {t}"))
158            }
159        }
160    }
161}
162
163impl From<MatrixKeysStoreError> for MatrixError {
164    fn from(e: MatrixKeysStoreError) -> Self {
165        match e {
166            MatrixKeysStoreError::Db(e) => MatrixError::from(e),
167            MatrixKeysStoreError::OneTimeKeyConflict(key_id) => {
168                MatrixError::invalid_param(format!("One-time key {key_id} already exists with different content"))
169            }
170            MatrixKeysStoreError::WrongBackupVersion => MatrixError::wrong_room_keys_version(None),
171        }
172    }
173}
174
175impl IntoResponse for MatrixError {
176    fn into_response(self) -> Response {
177        let status = StatusCode::from_u16(self.status).unwrap_or(StatusCode::INTERNAL_SERVER_ERROR);
178        (status, Json(self)).into_response()
179    }
180}
181
182impl From<serde_json::Error> for MatrixError {
183    fn from(e: serde_json::Error) -> Self {
184        tracing::error!("messenger JSON error: {e}");
185        Self::bad_json("Malformed JSON")
186    }
187}