1use arminius_lib_component::{
7 MessageTokenParseError, MessagesToken,
8 error::{
9 EventNotFound, FeatureNotImplemented, Forbidden, InputJsonError, InternalServerError,
10 MatrixApiError, RoomNotFound,
11 },
12 ruma_requests::ArminiusResponse,
13};
14use arminius_lib_events::{Draft, Verified};
15use arminius_lib_macros::{FromDbError, FromNatsError};
16use arminius_lib_room_service_messages::EventProvenance;
17use axum::{http::StatusCode, response::IntoResponse};
18use ruma::{
19 MilliSecondsSinceUnixEpoch, OwnedDeviceId, OwnedEventId, OwnedRoomId, OwnedServerName,
20 OwnedTransactionId, OwnedUserId,
21 api::{
22 client::{
23 membership::get_member_events,
24 redact::redact_event,
25 room::create_room,
26 state::{get_state_event_for_key, get_state_events},
27 },
28 error::{ErrorBody, ErrorKind},
29 },
30 events::{
31 AnyStateEvent, AnyTimelineEvent, TimelineEventType,
32 receipt::{ReceiptThread, ReceiptType},
33 room::member::RoomMemberEventContent,
34 },
35 serde::Raw,
36};
37use serde::{Deserialize, Serialize, de::DeserializeOwned};
38
39use crate::{
40 ruma_request_wrapper::SerializingRumaRequestWrapper,
41 ruma_response_wrapper::SerializingRumaResponseWrapper,
42};
43
44pub type ErrorOf<T> = <T as APICall>::Error;
46
47#[expect(
48 missing_docs,
49 reason = "TODO(#2003): This exception predates us enabling `clippy::allow_attributes_without_reason`, so we don't know why it was enabled"
50)]
51#[derive(thiserror::Error, Debug, MatrixApiError, Serialize, Deserialize)]
52#[matrix_api_error(status_code = StatusCode::NOT_FOUND, error_kind = ErrorKind::NotFound)]
53#[error("Unknown command: {0}")]
54pub struct UnknownCommand(pub String);
55
56pub trait APICall {
58 const ENDPOINT: &str;
60 type Request: Serialize + DeserializeOwned;
62 type Response: Serialize + DeserializeOwned;
64 type Error: Serialize + DeserializeOwned + IntoResponse + From<InternalServerError>;
66}
67
68#[derive(Debug)]
70pub struct AddNewEvent;
71
72#[expect(
73 missing_docs,
74 reason = "TODO(#2003): This exception predates us enabling `clippy::allow_attributes_without_reason`, so we don't know why it was enabled"
75)]
76#[derive(Debug, Serialize, Deserialize)]
77pub struct NewEvent {
78 pub event_type: TimelineEventType,
79 pub sender: OwnedUserId,
80 pub body: serde_json::Value,
82 pub state_key: Option<String>,
83 pub timestamp: Option<MilliSecondsSinceUnixEpoch>,
85 pub transaction_information: Option<TransactionInformation>,
87}
88
89#[expect(
90 missing_docs,
91 reason = "TODO(#2003): This exception predates us enabling `clippy::allow_attributes_without_reason`, so we don't know why it was enabled"
92)]
93#[derive(Debug, Serialize, Deserialize)]
94pub struct TransactionInformation {
95 pub device_id: OwnedDeviceId,
96 pub transaction_id: OwnedTransactionId,
97}
98
99pub const MAX_TRANSACTIONID_LENGTH: usize = 255;
104
105#[expect(
106 missing_docs,
107 reason = "TODO(#2003): This exception predates us enabling `clippy::allow_attributes_without_reason`, so we don't know why it was enabled"
108)]
109#[derive(
110 thiserror::Error, MatrixApiError, Debug, Serialize, Deserialize, FromDbError, FromNatsError,
111)]
112pub enum AddNewEventError {
113 #[error(transparent)]
116 InternalServerError(#[from] InternalServerError),
117 #[error(transparent)]
118 EventNotFound(#[from] EventNotFound),
119 #[error(transparent)]
120 RoomNotFound(#[from] RoomNotFound),
121 #[error(transparent)]
122 InputJsonError(#[from] InputJsonError),
123 #[matrix_api_error(status_code = StatusCode::FORBIDDEN, error_kind = ErrorKind::Forbidden)]
125 #[error("Forbidden: {0}")]
126 EventForbidden(String),
127 #[matrix_api_error(status_code = StatusCode::BAD_REQUEST, error_kind = ErrorKind::Unknown)]
128 #[error("Tried creating a member event, but could not parse the Matrix ID: {0}")]
129 InvalidMxIdError(String),
130 #[matrix_api_error(status_code = StatusCode::BAD_REQUEST, error_kind = ErrorKind::Unknown)]
131 #[error("m.room.member is a state event, but no state key was provided")]
132 StateEventRequiresStateKey,
133 #[error(transparent)]
134 AddRoomMemberEventError(#[from] AddRoomMemberEventError),
135 #[matrix_api_error(status_code = StatusCode::BAD_REQUEST, error_kind = ErrorKind::TooLarge)]
136 #[error(
137 "Transaction ID too large. Arminius only accepts transaction IDS up to {MAX_TRANSACTIONID_LENGTH} characters"
138 )]
139 TransactionIdTooLarge,
140}
141
142impl APICall for AddNewEvent {
143 const ENDPOINT: &str = "add_event";
144 type Request = NewEvent;
145 type Response = OwnedEventId;
146 type Error = AddNewEventError;
147}
148
149#[derive(thiserror::Error, Debug, MatrixApiError, Serialize, Deserialize)]
151#[matrix_api_error(status_code = StatusCode::BAD_REQUEST, error_kind = ErrorKind::UnsupportedRoomVersion)]
152#[error("This server does not support creating version {0} rooms")]
153pub struct UnsupportedRoomVersionError(pub String);
154
155#[expect(
156 missing_docs,
157 reason = "TODO(#2003): This exception predates us enabling `clippy::allow_attributes_without_reason`, so we don't know why it was enabled"
158)]
159#[derive(
160 thiserror::Error, MatrixApiError, Debug, Serialize, Deserialize, FromDbError, FromNatsError,
161)]
162pub enum CreateRoomError {
163 #[error(transparent)]
166 InternalServerError(#[from] InternalServerError),
167 #[error(transparent)]
168 EventNotFound(#[from] EventNotFound),
169 #[error(transparent)]
170 InputJsonError(#[from] InputJsonError),
171 #[error(transparent)]
172 FeatureNotImplemented(#[from] FeatureNotImplemented),
173 #[error(transparent)]
174 UnsupportedRoomVersionError(#[from] UnsupportedRoomVersionError),
175}
176
177impl From<serde_json::Error> for CreateRoomError {
178 fn from(value: serde_json::Error) -> Self {
179 InputJsonError::from(value).into()
180 }
181}
182
183#[derive(Debug, Serialize, Deserialize)]
185pub struct CreateRoomRequest {
186 pub creator: OwnedUserId,
188 pub request: SerializingRumaRequestWrapper<create_room::v3::Request>,
190 pub creator_join_event_contents: RoomMemberEventContent,
195}
196
197#[derive(Debug)]
199pub struct CreateRoom;
200
201impl APICall for CreateRoom {
202 const ENDPOINT: &str = "create";
203 type Request = CreateRoomRequest;
204 type Response = OwnedRoomId;
205 type Error = CreateRoomError;
206}
207
208#[expect(
209 missing_docs,
210 reason = "TODO(#2003): This exception predates us enabling `clippy::allow_attributes_without_reason`, so we don't know why it was enabled"
211)]
212#[derive(thiserror::Error, MatrixApiError, Debug, Serialize, Deserialize, FromDbError)]
213pub enum GetRoomEventError {
214 #[error(transparent)]
217 InternalServerError(#[from] InternalServerError),
218 #[error(transparent)]
219 EventNotFound(#[from] EventNotFound),
220 #[error(transparent)]
221 InputJsonError(#[from] InputJsonError),
222}
223
224#[derive(Debug)]
226pub struct GetRoomEvent;
227
228impl APICall for GetRoomEvent {
229 const ENDPOINT: &str = "get_room_event";
230 type Request = (OwnedUserId, OwnedDeviceId, OwnedEventId);
231 type Response = Raw<AnyTimelineEvent>;
232 type Error = GetRoomEventError;
233}
234
235#[expect(
236 missing_docs,
237 reason = "TODO(#2003): This exception predates us enabling `clippy::allow_attributes_without_reason`, so we don't know why it was enabled"
238)]
239#[derive(
240 thiserror::Error, MatrixApiError, Debug, Serialize, Deserialize, FromDbError, FromNatsError,
241)]
242pub enum GetRoomMembersError {
243 #[error(transparent)]
246 InternalServerError(#[from] InternalServerError),
247 #[error(transparent)]
248 EventNotFound(#[from] EventNotFound),
249 #[error(transparent)]
250 InputJsonError(#[from] InputJsonError),
251 #[error(transparent)]
252 RoomNotFound(#[from] RoomNotFound),
253 #[error(transparent)]
254 FeatureNotImplemented(#[from] FeatureNotImplemented),
255}
256
257#[derive(Debug)]
259pub struct GetRoomMembers;
260
261impl APICall for GetRoomMembers {
262 const ENDPOINT: &str = "room_members";
263 type Request = (SerializingRumaRequestWrapper<get_member_events::v3::Request>, OwnedUserId);
264 type Response = SerializingRumaResponseWrapper<get_member_events::v3::Response>;
265 type Error = GetRoomMembersError;
266}
267
268#[expect(
269 missing_docs,
270 reason = "TODO(#2003): This exception predates us enabling `clippy::allow_attributes_without_reason`, so we don't know why it was enabled"
271)]
272#[derive(
273 thiserror::Error, MatrixApiError, Debug, Serialize, Deserialize, FromDbError, FromNatsError,
274)]
275pub enum GetStateEventsError {
276 #[error(transparent)]
279 InternalServerError(#[from] InternalServerError),
280 #[error(transparent)]
281 EventNotFound(#[from] EventNotFound),
282 #[error(transparent)]
283 RoomNotFound(#[from] RoomNotFound),
284}
285
286#[derive(Debug)]
288pub struct GetStateEvents;
289
290impl APICall for GetStateEvents {
291 const ENDPOINT: &str = "get_state_events";
292 type Request = (SerializingRumaRequestWrapper<get_state_events::v3::Request>, OwnedUserId);
293 type Response = SerializingRumaResponseWrapper<get_state_events::v3::Response>;
294 type Error = GetStateEventsError;
295}
296
297#[expect(
298 missing_docs,
299 reason = "TODO(#2003): This exception predates us enabling `clippy::allow_attributes_without_reason`, so we don't know why it was enabled"
300)]
301#[derive(
302 thiserror::Error, MatrixApiError, Debug, Serialize, Deserialize, FromDbError, FromNatsError,
303)]
304pub enum GetStateEventsForKeyError {
305 #[error(transparent)]
308 InternalServerError(#[from] InternalServerError),
309 #[error(transparent)]
310 EventNotFound(#[from] EventNotFound),
311 #[error(transparent)]
312 InputJsonError(#[from] InputJsonError),
313}
314
315#[derive(Debug)]
317pub struct GetStateEventsForKey;
318
319impl APICall for GetStateEventsForKey {
320 const ENDPOINT: &str = "get_state_event";
321 type Request =
322 (SerializingRumaRequestWrapper<get_state_event_for_key::v3::Request>, OwnedUserId);
323 type Response = SerializingRumaResponseWrapper<get_state_event_for_key::v3::Response>;
324 type Error = GetStateEventsForKeyError;
325}
326
327#[expect(
328 missing_docs,
329 reason = "TODO(#2003): This exception predates us enabling `clippy::allow_attributes_without_reason`, so we don't know why it was enabled"
330)]
331#[derive(thiserror::Error, MatrixApiError, Debug, Serialize, Deserialize, FromNatsError)]
332pub enum RedactEventError {
333 #[error(transparent)]
336 InternalServerError(#[from] InternalServerError),
337 #[error(transparent)]
338 EventNotFound(#[from] EventNotFound),
339 #[error(transparent)]
340 InputJsonError(#[from] InputJsonError),
341 #[error(transparent)]
342 Forbidden(#[from] Forbidden),
343 #[error(transparent)]
344 RoomNotFound(#[from] RoomNotFound),
345 #[error(transparent)]
346 FeatureNotImplemented(#[from] FeatureNotImplemented),
347}
348
349#[derive(Debug)]
351pub struct RedactEvent;
352
353impl APICall for RedactEvent {
354 const ENDPOINT: &str = "redact_room_event";
355 type Request = (SerializingRumaRequestWrapper<redact_event::v3::Request>, OwnedUserId);
356 type Response = SerializingRumaResponseWrapper<redact_event::v3::Response>;
357 type Error = RedactEventError;
358}
359
360#[derive(Debug, Serialize, Deserialize)]
362pub struct GetStateEventsDiffRequestParameters {
363 pub at: MessagesToken,
366 pub diff: Option<MessagesToken>,
370}
371
372#[derive(Debug, Serialize, Deserialize)]
374pub struct GetStateEventsDiffResponse {
375 pub room_state: Vec<Raw<AnyStateEvent>>,
379}
380
381#[derive(Debug)]
383pub struct GetStateEventsDiffs;
384
385#[expect(
386 missing_docs,
387 reason = "TODO(#2003): This exception predates us enabling `clippy::allow_attributes_without_reason`, so we don't know why it was enabled"
388)]
389#[derive(thiserror::Error, MatrixApiError, Debug, Serialize, Deserialize, FromDbError)]
390pub enum GetStateEventsDiffError {
391 #[error(transparent)]
394 InternalServerError(#[from] InternalServerError),
395 #[error(transparent)]
396 EventNotFound(#[from] EventNotFound),
397 #[error(transparent)]
398 RoomNotFound(#[from] RoomNotFound),
399 #[error(transparent)]
400 MessageTokenParseError(#[from] MessageTokenParseError),
401}
402
403impl APICall for GetStateEventsDiffs {
404 const ENDPOINT: &str = "get_state_events_diffs";
405 type Request = GetStateEventsDiffRequestParameters;
406 type Response = GetStateEventsDiffResponse;
407 type Error = GetStateEventsDiffError;
408}
409
410#[derive(Debug)]
412pub struct ForgetRoom;
413
414#[expect(
415 missing_docs,
416 reason = "TODO(#2003): This exception predates us enabling `clippy::allow_attributes_without_reason`, so we don't know why it was enabled"
417)]
418#[derive(
419 thiserror::Error, MatrixApiError, Debug, Serialize, Deserialize, FromDbError, FromNatsError,
420)]
421pub enum ForgetRoomError {
422 #[error(transparent)]
423 InternalServerError(#[from] InternalServerError),
424 #[matrix_api_error(status_code = StatusCode::BAD_REQUEST, error_kind = ErrorKind::Unknown)]
425 #[error("You cannot forget a room you're still in")]
426 StillInRoom,
427 #[error(transparent)]
428 RoomNotFound(#[from] RoomNotFound),
429}
430
431impl APICall for ForgetRoom {
432 const ENDPOINT: &str = "forget_room";
433 type Request = OwnedUserId;
434 type Response = ();
435 type Error = ForgetRoomError;
436}
437
438#[derive(Debug)]
443pub struct AddRoomMemberEvent;
444
445#[derive(Debug, thiserror::Error, Serialize, Deserialize)]
450#[error("Custom matrix error")]
451pub struct FederatedMatrixError(pub arminius_server_federation_output::ErrorResponse);
452
453impl MatrixApiError for FederatedMatrixError {
454 fn create_matrix_error(&self) -> ruma::api::error::Error {
455 ErrorBody::Json(self.0.matrix_error.clone()).into_error(self.0.http_error)
456 }
457}
458
459impl IntoResponse for FederatedMatrixError {
460 fn into_response(self) -> axum::response::Response {
461 ArminiusResponse::from(self.create_matrix_error()).into_response()
462 }
463}
464
465#[expect(
466 missing_docs,
467 reason = "TODO(#2003): This exception predates us enabling `clippy::allow_attributes_without_reason`, so we don't know why it was enabled"
468)]
469#[derive(
470 thiserror::Error, MatrixApiError, Debug, Serialize, Deserialize, FromDbError, FromNatsError,
471)]
472pub enum AddRoomMemberEventError {
473 #[error(transparent)]
474 InternalServerError(#[from] InternalServerError),
475 #[error(transparent)]
476 RoomNotFound(#[from] RoomNotFound),
477 #[error(transparent)]
478 FeatureNotImplemented(#[from] FeatureNotImplemented),
479 #[error(transparent)]
480 InputJsonError(#[from] InputJsonError),
481 #[error("Not allowed to add member event: {0}")]
483 #[matrix_api_error(status_code = StatusCode::FORBIDDEN, error_kind = ErrorKind::Forbidden)]
484 EventForbidden(String),
485 #[error(transparent)]
486 UnsupportedRoomVersionError(#[from] UnsupportedRoomVersionError),
487 #[error(transparent)]
488 FederatedMatrixError(#[from] FederatedMatrixError),
489}
490
491#[derive(Debug, Serialize, Deserialize)]
493pub struct AddRoomMemberEventRequest {
494 pub via: Vec<OwnedServerName>,
505 pub member_event_content: RoomMemberEventContent,
507 pub member: OwnedUserId,
510 pub sender: OwnedUserId,
512}
513
514impl APICall for AddRoomMemberEvent {
515 const ENDPOINT: &str = "add_room_member_event";
516 type Request = AddRoomMemberEventRequest;
517 type Response = ();
518 type Error = AddRoomMemberEventError;
519}
520
521#[derive(Debug)]
523pub struct AddFederatedEvent;
524
525#[derive(Debug, Serialize, Deserialize)]
527pub struct AddFederatedEventCommand {
528 pub pdu: Box<serde_json::value::RawValue>,
530 pub provenance: EventProvenance,
542}
543
544#[expect(
545 missing_docs,
546 reason = "TODO(#2003): This exception predates us enabling `clippy::allow_attributes_without_reason`, so we don't know why it was enabled"
547)]
548#[derive(
549 thiserror::Error, MatrixApiError, Debug, Serialize, Deserialize, FromDbError, FromNatsError,
550)]
551pub enum AddFederatedEventError {
552 #[error(transparent)]
553 InternalServerError(#[from] InternalServerError),
554 #[error(transparent)]
555 InputJsonError(#[from] InputJsonError),
556 #[error(transparent)]
557 FeatureNotImplemented(#[from] FeatureNotImplemented),
558 #[error(transparent)]
559 RoomNotFound(#[from] RoomNotFound),
560 #[error(transparent)]
561 Forbidden(#[from] Forbidden),
562 #[matrix_api_error(status_code = StatusCode::FORBIDDEN, error_kind = ErrorKind::Forbidden)]
563 #[error("Unauthorized: {message}")]
564 Unauthorized { message: String },
565}
566
567impl APICall for AddFederatedEvent {
568 const ENDPOINT: &str = "add_fed_event";
569
570 type Request = AddFederatedEventCommand;
571
572 type Response = OwnedEventId;
573
574 type Error = AddFederatedEventError;
575}
576
577#[derive(Debug)]
579pub struct ReadReceipt;
580
581#[expect(
582 missing_docs,
583 reason = "TODO(#2003): This exception predates us enabling `clippy::allow_attributes_without_reason`, so we don't know why it was enabled"
584)]
585#[derive(
586 thiserror::Error, MatrixApiError, Debug, Serialize, Deserialize, FromDbError, FromNatsError,
587)]
588pub enum ReadReceiptError {
589 #[error(transparent)]
590 InternalServerError(#[from] InternalServerError),
591 #[error(transparent)]
592 EventNotFound(#[from] EventNotFound),
593 #[error(transparent)]
594 RoomNotFound(#[from] RoomNotFound),
595 #[matrix_api_error(status_code = StatusCode::BAD_REQUEST, error_kind = ErrorKind::InvalidParam)]
598 #[error("Invalid thread id: {0}")]
599 InvalidThreadId(String),
600 #[error(transparent)]
601 FeatureNotImplemented(#[from] FeatureNotImplemented),
602}
603
604#[derive(Debug, Deserialize, Serialize)]
606pub struct ReadReceiptRequest {
607 pub user_id: OwnedUserId,
609 pub receipt_type: ReceiptType,
611 pub event_id: OwnedEventId,
613 #[serde(default, skip_serializing_if = "ruma::serde::is_default")]
618 pub thread: ReceiptThread,
619 pub ts: chrono::DateTime<chrono::Utc>,
621 pub provenance: EventProvenance,
627}
628
629impl APICall for ReadReceipt {
630 const ENDPOINT: &str = "read_receipt";
631 type Request = ReadReceiptRequest;
632 type Response = ();
633 type Error = ReadReceiptError;
634}
635
636#[derive(Debug)]
638pub struct SetFullyReadMarker;
639
640#[expect(
641 missing_docs,
642 reason = "TODO(#2003): This exception predates us enabling `clippy::allow_attributes_without_reason`, so we don't know why it was enabled"
643)]
644#[derive(
645 thiserror::Error, MatrixApiError, Debug, Serialize, Deserialize, FromDbError, FromNatsError,
646)]
647pub enum ReadMarkerError {
648 #[error(transparent)]
649 InternalServerError(#[from] InternalServerError),
650 #[error(transparent)]
651 EventNotFound(#[from] EventNotFound),
652 #[error(transparent)]
653 RoomNotFound(#[from] RoomNotFound),
654}
655
656#[derive(Debug, Deserialize, Serialize)]
658pub struct ReadMarkerRequest {
659 pub user_id: OwnedUserId,
661 pub event_id: OwnedEventId,
663}
664
665impl APICall for SetFullyReadMarker {
666 const ENDPOINT: &str = "read_marker";
667 type Request = ReadMarkerRequest;
668 type Response = ();
669 type Error = ReadMarkerError;
670}
671
672#[derive(Debug)]
674pub struct ContextualizeEvent;
675
676#[expect(
677 missing_docs,
678 reason = "TODO(#2003): This exception predates us enabling `clippy::allow_attributes_without_reason`, so we don't know why it was enabled"
679)]
680#[derive(
681 thiserror::Error, MatrixApiError, Debug, Serialize, Deserialize, FromDbError, FromNatsError,
682)]
683pub enum ContextualizeEventError {
684 #[error(transparent)]
687 InternalServerError(#[from] InternalServerError),
688 #[error(transparent)]
689 RoomNotFound(#[from] RoomNotFound),
690 #[error("Forbidden: {0}")]
692 #[matrix_api_error(status_code = StatusCode::FORBIDDEN, error_kind = ErrorKind::Forbidden)]
693 EventForbidden(String),
694}
695
696impl APICall for ContextualizeEvent {
697 const ENDPOINT: &str = "contextualize_event";
698 type Request = arminius_lib_events::RoomEvent<Draft>;
699 type Response = arminius_lib_events::RoomEvent<Verified>;
700 type Error = ContextualizeEventError;
701}