ruma_client_api/message/
get_message_events.rs

1//! `GET /_matrix/client/*/rooms/{roomId}/messages`
2//!
3//! Get message events for a room.
4
5pub mod v3 {
6    //! `/v3/` ([spec])
7    //!
8    //! [spec]: https://spec.matrix.org/latest/client-server-api/#get_matrixclientv3roomsroomidmessages
9
10    use js_int::{UInt, uint};
11    use ruma_common::{
12        OwnedRoomId,
13        api::{Direction, auth_scheme::AccessToken, request, response},
14        metadata,
15        serde::Raw,
16    };
17    use ruma_events::{AnyStateEvent, AnyTimelineEvent};
18
19    use crate::filter::RoomEventFilter;
20
21    metadata! {
22        method: GET,
23        rate_limited: false,
24        authentication: AccessToken,
25        history: {
26            1.0 => "/_matrix/client/r0/rooms/{room_id}/messages",
27            1.1 => "/_matrix/client/v3/rooms/{room_id}/messages",
28        }
29    }
30
31    /// Request type for the `get_message_events` endpoint.
32    #[request(error = crate::Error)]
33    pub struct Request {
34        /// The room to get events from.
35        #[ruma_api(path)]
36        pub room_id: OwnedRoomId,
37
38        /// The token to start returning events from.
39        ///
40        /// This token can be obtained from a `prev_batch` token returned for each room by the
41        /// sync endpoint, or from a `start` or `end` token returned by a previous request to
42        /// this endpoint.
43        ///
44        /// If this is `None`, the server will return messages from the start or end of the
45        /// history visible to the user, depending on the value of [`dir`][Self::dir].
46        #[ruma_api(query)]
47        pub from: Option<String>,
48
49        /// The token to stop returning events at.
50        ///
51        /// This token can be obtained from a `prev_batch` token returned for each room by the
52        /// sync endpoint, or from a `start` or `end` token returned by a previous request to
53        /// this endpoint.
54        #[serde(skip_serializing_if = "Option::is_none")]
55        #[ruma_api(query)]
56        pub to: Option<String>,
57
58        /// The direction to return events from.
59        #[ruma_api(query)]
60        pub dir: Direction,
61
62        /// The maximum number of events to return.
63        ///
64        /// Default: `10`.
65        #[ruma_api(query)]
66        #[serde(default = "default_limit", skip_serializing_if = "is_default_limit")]
67        pub limit: UInt,
68
69        /// A [`RoomEventFilter`] to filter returned events with.
70        #[ruma_api(query)]
71        #[serde(
72            with = "ruma_common::serde::json_string",
73            default,
74            skip_serializing_if = "RoomEventFilter::is_empty"
75        )]
76        pub filter: RoomEventFilter,
77    }
78
79    /// Response type for the `get_message_events` endpoint.
80    #[response(error = crate::Error)]
81    #[derive(Default)]
82    pub struct Response {
83        /// The token the pagination starts from.
84        pub start: String,
85
86        /// The token the pagination ends at.
87        #[serde(skip_serializing_if = "Option::is_none")]
88        pub end: Option<String>,
89
90        /// A list of room events.
91        #[serde(default)]
92        pub chunk: Vec<Raw<AnyTimelineEvent>>,
93
94        /// A list of state events relevant to showing the `chunk`.
95        #[serde(default, skip_serializing_if = "Vec::is_empty")]
96        pub state: Vec<Raw<AnyStateEvent>>,
97    }
98
99    impl Request {
100        /// Creates a new `Request` with the given room ID and direction.
101        ///
102        /// All other parameters will be defaulted.
103        pub fn new(room_id: OwnedRoomId, dir: Direction) -> Self {
104            Self {
105                room_id,
106                from: None,
107                to: None,
108                dir,
109                limit: default_limit(),
110                filter: RoomEventFilter::default(),
111            }
112        }
113
114        /// Creates a new `Request` with the given room ID and `dir` set to `Backward`.
115        ///
116        /// If the returned request is sent without `from` being set, pagination will start at the
117        /// end of (the accessible part of) the room timeline.
118        ///
119        /// # Example
120        ///
121        /// ```rust
122        /// # use ruma_client_api::message::get_message_events;
123        /// # let room_id = ruma_common::owned_room_id!("!a:example.org");
124        /// # let token = "prev_batch token".to_owned();
125        /// let request = get_message_events::v3::Request::backward(room_id).from(token);
126        /// ```
127        pub fn backward(room_id: OwnedRoomId) -> Self {
128            Self::new(room_id, Direction::Backward)
129        }
130
131        /// Creates a new `Request` with the given room ID and `dir` set to `Forward`.
132        ///
133        /// If the returned request is sent without `from` being set, pagination will start at the
134        /// beginning of (the accessible part of) the room timeline.
135        ///
136        /// # Example
137        ///
138        /// ```rust
139        /// # use ruma_client_api::message::get_message_events;
140        /// # let room_id = ruma_common::owned_room_id!("!a:example.org");
141        /// # let token = "end token".to_owned();
142        /// let request = get_message_events::v3::Request::forward(room_id).from(token);
143        /// ```
144        pub fn forward(room_id: OwnedRoomId) -> Self {
145            Self::new(room_id, Direction::Forward)
146        }
147
148        /// Creates a new `Request` from `self` with the `from` field set to the given value.
149        ///
150        /// Since the field is public, you can also assign to it directly. This method merely acts
151        /// as a shorthand for that, because it is very common to set this field.
152        pub fn from(self, from: impl Into<Option<String>>) -> Self {
153            Self { from: from.into(), ..self }
154        }
155    }
156
157    impl Response {
158        /// Creates an empty `Response`.
159        pub fn new() -> Self {
160            Default::default()
161        }
162    }
163
164    fn default_limit() -> UInt {
165        uint!(10)
166    }
167
168    #[cfg(feature = "client")]
169    #[allow(clippy::trivially_copy_pass_by_ref)]
170    fn is_default_limit(val: &UInt) -> bool {
171        *val == default_limit()
172    }
173
174    #[cfg(all(test, feature = "client"))]
175    mod tests {
176        use std::borrow::Cow;
177
178        use js_int::uint;
179        use ruma_common::{
180            api::{
181                Direction, MatrixVersion, OutgoingRequest, SupportedVersions,
182                auth_scheme::SendAccessToken,
183            },
184            owned_room_id,
185        };
186
187        use super::Request;
188        use crate::filter::{LazyLoadOptions, RoomEventFilter};
189
190        #[test]
191        fn serialize_some_room_event_filter() {
192            let room_id = owned_room_id!("!roomid:example.org");
193            let rooms = vec![room_id.to_owned()];
194            let filter = RoomEventFilter {
195                lazy_load_options: LazyLoadOptions::Enabled { include_redundant_members: true },
196                rooms: Some(rooms),
197                not_rooms: vec![
198                    owned_room_id!("!room:example.org"),
199                    owned_room_id!("!room2:example.org"),
200                    owned_room_id!("!room3:example.org"),
201                ],
202                not_types: vec!["type".into()],
203                ..Default::default()
204            };
205            let req = Request {
206                room_id,
207                from: Some("token".to_owned()),
208                to: Some("token2".to_owned()),
209                dir: Direction::Backward,
210                limit: uint!(0),
211                filter,
212            };
213            let supported = SupportedVersions {
214                versions: [MatrixVersion::V1_1].into(),
215                features: Default::default(),
216            };
217
218            let request: http::Request<Vec<u8>> = req
219                .try_into_http_request(
220                    "https://homeserver.tld",
221                    SendAccessToken::IfRequired("auth_tok"),
222                    Cow::Owned(supported),
223                )
224                .unwrap();
225            assert_eq!(
226                "from=token\
227                 &to=token2\
228                 &dir=b\
229                 &limit=0\
230                 &filter=%7B%22not_types%22%3A%5B%22type%22%5D%2C%22not_rooms%22%3A%5B%22%21room%3Aexample.org%22%2C%22%21room2%3Aexample.org%22%2C%22%21room3%3Aexample.org%22%5D%2C%22rooms%22%3A%5B%22%21roomid%3Aexample.org%22%5D%2C%22lazy_load_members%22%3Atrue%2C%22include_redundant_members%22%3Atrue%7D",
231                request.uri().query().unwrap()
232            );
233        }
234
235        #[test]
236        fn serialize_default_room_event_filter() {
237            let room_id = owned_room_id!("!roomid:example.org");
238            let req = Request {
239                room_id,
240                from: Some("token".to_owned()),
241                to: Some("token2".to_owned()),
242                dir: Direction::Backward,
243                limit: uint!(0),
244                filter: RoomEventFilter::default(),
245            };
246            let supported = SupportedVersions {
247                versions: [MatrixVersion::V1_1].into(),
248                features: Default::default(),
249            };
250
251            let request = req
252                .try_into_http_request::<Vec<u8>>(
253                    "https://homeserver.tld",
254                    SendAccessToken::IfRequired("auth_tok"),
255                    Cow::Owned(supported),
256                )
257                .unwrap();
258            assert_eq!("from=token&to=token2&dir=b&limit=0", request.uri().query().unwrap(),);
259        }
260    }
261}