Skip to main content

ruma_client_api/delayed_events/
send_delayed_event.rs

1//! `PUT /_matrix/client/*/rooms/{roomId}/delayed_event/{eventType}/{txnId}`
2//!
3//! Send a delayed event (a scheduled message) to a room.
4
5pub mod unstable {
6    //! `msc4140` ([MSC])
7    //!
8    //! [MSC]: https://github.com/matrix-org/matrix-spec-proposals/pull/4140
9
10    use std::time::Duration;
11
12    use ruma_common::{
13        OwnedRoomId, OwnedTransactionId,
14        api::{auth_scheme::AccessToken, request, response},
15        metadata,
16        serde::Raw,
17    };
18    use ruma_events::{AnyTimelineEventContent, TimelineEventType};
19
20    metadata! {
21        method: PUT,
22        rate_limited: true,
23        authentication: AccessToken,
24        history: {
25            unstable("org.matrix.msc4140") => "/_matrix/client/unstable/org.matrix.msc4140/rooms/{room_id}/delayed_event/{event_type}/{txn_id}",
26        }
27    }
28    /// Request type for the [`send_delayed_event`](crate::delayed_events::send_delayed_event)
29    /// endpoint.
30    #[request]
31    pub struct Request {
32        /// The room to send the event to.
33        #[ruma_api(path)]
34        pub room_id: OwnedRoomId,
35
36        /// The type of event to send.
37        #[ruma_api(path)]
38        pub event_type: TimelineEventType,
39
40        /// The transaction ID for this event.
41        ///
42        /// Clients should generate a unique ID across requests within the
43        /// same session. A session is identified by an access token, and
44        /// persists when the [access token is refreshed].
45        ///
46        /// It will be used by the server to ensure idempotency of requests.
47        ///
48        /// [access token is refreshed]: https://spec.matrix.org/v1.18/client-server-api/#refreshing-access-tokens
49        #[ruma_api(path)]
50        pub txn_id: OwnedTransactionId,
51
52        /// The duration that the server should wait before sending this event
53        #[serde(with = "ruma_common::serde::duration::ms")]
54        pub delay: Duration,
55
56        /// The State Key if the event is a state event, nothing otherwise
57        #[serde(skip_serializing_if = "Option::is_none")]
58        pub state_key: Option<String>,
59
60        /// The event content to send.
61        pub content: Raw<AnyTimelineEventContent>,
62    }
63
64    /// Response type for the
65    /// [`send_delayed_event`](crate::delayed_events::send_delayed_event) endpoint.
66    #[response]
67    pub struct Response {
68        /// The `delay_id` generated for this delayed event. Used to interact with delayed events.
69        pub delay_id: String,
70    }
71
72    impl Request {
73        /// Creates a new `Request` with the given room id, transaction id, `delay_parameters` and
74        /// event content.
75        ///
76        /// # Errors
77        ///
78        /// Since `Request` stores the request body in serialized form, this function can fail if
79        /// `T`s [`::serde::Serialize`] implementation can fail.
80        pub fn new(
81            room_id: OwnedRoomId,
82            txn_id: OwnedTransactionId,
83            delay: Duration,
84            state_key: Option<String>,
85            content: &AnyTimelineEventContent,
86        ) -> serde_json::Result<Self> {
87            Ok(Self {
88                room_id,
89                txn_id,
90                event_type: content.event_type(),
91                state_key,
92                delay,
93                content: Raw::new(content)?,
94            })
95        }
96
97        /// Creates a new `Request` with the given room id, transaction id, event type,
98        /// `delay_parameters` and raw event content.
99        pub fn new_raw(
100            event_type: TimelineEventType,
101            room_id: OwnedRoomId,
102            txn_id: OwnedTransactionId,
103            delay: Duration,
104            state_key: Option<String>,
105            content: Raw<AnyTimelineEventContent>,
106        ) -> serde_json::Result<Self> {
107            Ok(Self { room_id, txn_id, event_type, state_key, delay, content })
108        }
109    }
110
111    impl Response {
112        /// Creates a new `Response` with the tokens required to control the delayed event using the
113        /// [`crate::delayed_events::update_delayed_event::unstable_v2::Request`] request.
114        pub fn new(delay_id: String) -> Self {
115            Self { delay_id }
116        }
117    }
118
119    #[cfg(all(test, feature = "client"))]
120    mod client_tests {
121        use std::borrow::Cow;
122
123        use ruma_common::{
124            api::{
125                MatrixVersion, OutgoingRequest, SupportedVersions, auth_scheme::SendAccessToken,
126            },
127            owned_room_id,
128        };
129        use ruma_events::{AnyMessageLikeEventContent, room::message::RoomMessageEventContent};
130        use serde_json::{Value as JsonValue, json};
131        use web_time::Duration;
132
133        use super::Request;
134
135        #[test]
136        fn serialize_send_delayed_event_request() {
137            let room_id = owned_room_id!("!roomid:example.org");
138            let supported = SupportedVersions {
139                versions: [MatrixVersion::V1_1].into(),
140                features: Default::default(),
141            };
142
143            let req = Request::new(
144                room_id,
145                "1234".into(),
146                Duration::from_millis(103),
147                None,
148                &AnyMessageLikeEventContent::from(RoomMessageEventContent::text_plain("test"))
149                    .into(),
150            )
151            .unwrap();
152            let request: http::Request<Vec<u8>> = req
153                .try_into_http_request(
154                    "https://homeserver.tld",
155                    SendAccessToken::IfRequired("auth_tok"),
156                    Cow::Owned(supported),
157                )
158                .unwrap();
159            let (parts, body) = request.into_parts();
160            assert_eq!(
161                "https://homeserver.tld/_matrix/client/unstable/org.matrix.msc4140/rooms/!roomid:example.org/delayed_event/m.room.message/1234",
162                parts.uri.to_string()
163            );
164            assert_eq!("PUT", parts.method.to_string());
165            assert_eq!(
166                json!({"content":{"msgtype":"m.text","body":"test"}, "delay": 103}),
167                serde_json::from_str::<JsonValue>(std::str::from_utf8(&body).unwrap()).unwrap()
168            );
169        }
170    }
171
172    #[cfg(all(test, feature = "server"))]
173    mod server_tests {
174
175        use std::time::Duration;
176
177        use ruma_common::{OwnedTransactionId, api::IncomingRequest, owned_room_id};
178        use serde_json::json;
179
180        use super::Request;
181
182        #[test]
183        fn deserialize_send_delayed_events_request() {
184            let uri = http::Uri::builder()
185                .scheme("https")
186                .authority("matrix.org")
187                .path_and_query(
188                    "/_matrix/client/unstable/org.matrix.msc4140/rooms/!roomid:example.org/delayed_event/m.room.message/5678",
189                )
190                .build()
191                .unwrap();
192
193            let body = json!({"content":{"msgtype":"m.text","body":"test"}, "delay": 103});
194
195            let req = Request::try_from_http_request(
196                http::Request::builder().method("PUT").uri(uri).body(body.to_string()).unwrap(),
197                &["!roomid:example.org", "m.room.message", "5678"],
198            )
199            .unwrap();
200
201            assert_eq!(req.room_id, owned_room_id!("!roomid:example.org"));
202            assert_eq!(req.event_type, "m.room.message".into());
203            assert_eq!(req.txn_id, OwnedTransactionId::from("5678"));
204            assert_eq!(req.delay, Duration::from_millis(103));
205            assert_eq!(req.state_key, None);
206            assert_eq!(
207                serde_json::from_str::<serde_json::Value>(req.content.json().get()).unwrap(),
208                json!({"msgtype":"m.text","body":"test"}),
209            );
210        }
211    }
212}