mail4agent_server/
error.rs1use 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 pub(crate) 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 pub fn unauthorized(msg: impl Into<String>) -> Self {
52 Self::new(401, "M_UNAUTHORIZED", msg)
53 }
54
55 pub fn invalid_nick(msg: impl Into<String>) -> Self {
57 Self::new(400, "M4A_INVALID_NICK", msg)
58 }
59
60 pub fn nick_taken() -> Self {
62 Self::new(409, "M4A_NICK_TAKEN", "nick is taken")
63 }
64
65 pub fn nick_conflict() -> Self {
68 Self::new(409, "M4A_NICK_CONFLICT", "nick conflicts with an existing account; choose another nick")
69 }
70
71 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 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::F3Rejected(why) => MatrixError::forbidden(format!("Refused by the room's auth rules: {why}")),
157 MatrixStoreError::UnredactableEvent(t) => {
158 MatrixError::forbidden(format!("This event type cannot be redacted: {t}"))
159 }
160 }
161 }
162}
163
164impl From<MatrixKeysStoreError> for MatrixError {
165 fn from(e: MatrixKeysStoreError) -> Self {
166 match e {
167 MatrixKeysStoreError::Db(e) => MatrixError::from(e),
168 MatrixKeysStoreError::OneTimeKeyConflict(key_id) => {
169 MatrixError::invalid_param(format!("One-time key {key_id} already exists with different content"))
170 }
171 MatrixKeysStoreError::WrongBackupVersion => MatrixError::wrong_room_keys_version(None),
172 }
173 }
174}
175
176impl IntoResponse for MatrixError {
177 fn into_response(self) -> Response {
178 let status = StatusCode::from_u16(self.status).unwrap_or(StatusCode::INTERNAL_SERVER_ERROR);
179 (status, Json(self)).into_response()
180 }
181}
182
183impl From<serde_json::Error> for MatrixError {
184 fn from(e: serde_json::Error) -> Self {
185 tracing::error!("messenger JSON error: {e}");
186 Self::bad_json("Malformed JSON")
187 }
188}