ruma_client_api/state/
get_state_event_for_key.rs

1//! `GET /_matrix/client/*/rooms/{roomId}/state/{eventType}/{stateKey}`
2//!
3//! Get state events associated with a given key.
4
5pub mod v3 {
6    //! `/v3/` ([spec])
7    //!
8    //! [spec]: https://spec.matrix.org/latest/client-server-api/#get_matrixclientv3roomsroomidstateeventtypestatekey
9
10    use ruma_common::{
11        api::{response, Metadata},
12        metadata,
13        serde::Raw,
14        OwnedRoomId,
15    };
16    use ruma_events::{AnyStateEvent, AnyStateEventContent, StateEventType};
17    use serde_json::value::RawValue as RawJsonValue;
18
19    const METADATA: Metadata = metadata! {
20        method: GET,
21        rate_limited: false,
22        authentication: AccessToken,
23        history: {
24            1.0 => "/_matrix/client/r0/rooms/{room_id}/state/{event_type}/{state_key}",
25            1.1 => "/_matrix/client/v3/rooms/{room_id}/state/{event_type}/{state_key}",
26        }
27    };
28
29    /// Request type for the `get_state_events_for_key` endpoint.
30    #[derive(Clone, Debug)]
31    #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
32    pub struct Request {
33        /// The room to look up the state for.
34        pub room_id: OwnedRoomId,
35
36        /// The type of state to look up.
37        pub event_type: StateEventType,
38
39        /// The key of the state to look up.
40        pub state_key: String,
41
42        /// The format to use for the returned data.
43        pub format: StateEventFormat,
44    }
45
46    impl Request {
47        /// Creates a new `Request` with the given room ID, event type and state key.
48        pub fn new(room_id: OwnedRoomId, event_type: StateEventType, state_key: String) -> Self {
49            Self { room_id, event_type, state_key, format: StateEventFormat::default() }
50        }
51    }
52
53    /// The format to use for the returned data.
54    #[cfg_attr(feature = "client", derive(serde::Serialize))]
55    #[cfg_attr(feature = "server", derive(serde::Deserialize))]
56    #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
57    #[derive(Default, Debug, PartialEq, Clone, Copy)]
58    #[serde(rename_all = "lowercase")]
59    pub enum StateEventFormat {
60        /// Will return only the content of the state event.
61        ///
62        /// This is the default value if the format is unspecified in the request.
63        #[default]
64        Content,
65
66        /// Will return the entire event in the usual format suitable for clients, including fields
67        /// like event ID, sender and timestamp.
68        Event,
69    }
70
71    /// Response type for the `get_state_events_for_key` endpoint, either the `Raw` `AnyStateEvent`
72    /// or `AnyStateEventContent`.
73    ///
74    /// While it's possible to access the raw value directly, it's recommended you use the
75    /// provided helper methods to access it, and `From` to create it.
76    #[response(error = crate::Error)]
77    pub struct Response {
78        /// The full event (content) of the state event.
79        #[ruma_api(body)]
80        pub event_or_content: Box<RawJsonValue>,
81    }
82
83    impl From<Raw<AnyStateEvent>> for Response {
84        fn from(value: Raw<AnyStateEvent>) -> Self {
85            Self { event_or_content: value.into_json() }
86        }
87    }
88
89    impl From<Raw<AnyStateEventContent>> for Response {
90        fn from(value: Raw<AnyStateEventContent>) -> Self {
91            Self { event_or_content: value.into_json() }
92        }
93    }
94
95    impl Response {
96        /// Creates a new `Response` with the given event (content).
97        pub fn new(event_or_content: Box<RawJsonValue>) -> Self {
98            Self { event_or_content }
99        }
100
101        /// Returns an unchecked `Raw<AnyStateEvent>`.
102        ///
103        /// This method should only be used if you specified the `format` in the request to be
104        /// `StateEventFormat::Event`
105        pub fn into_event(self) -> Raw<AnyStateEvent> {
106            Raw::from_json(self.event_or_content)
107        }
108
109        /// Returns an unchecked `Raw<AnyStateEventContent>`.
110        ///
111        /// This method should only be used if you did not specify the `format` in the request, or
112        /// set it to be `StateEventFormat::Content`
113        ///
114        /// Since the inner type of the `Raw` does not implement `Deserialize`, you need to use
115        /// `.deserialize_as_unchecked::<T>()` or
116        /// `.cast_ref_unchecked::<T>().deserialize_with_type()` to deserialize it.
117        pub fn into_content(self) -> Raw<AnyStateEventContent> {
118            Raw::from_json(self.event_or_content)
119        }
120    }
121
122    #[cfg(feature = "client")]
123    impl ruma_common::api::OutgoingRequest for Request {
124        type EndpointError = crate::Error;
125        type IncomingResponse = Response;
126
127        const METADATA: Metadata = METADATA;
128
129        fn try_into_http_request<T: Default + bytes::BufMut>(
130            self,
131            base_url: &str,
132            access_token: ruma_common::api::SendAccessToken<'_>,
133            considering: &'_ ruma_common::api::SupportedVersions,
134        ) -> Result<http::Request<T>, ruma_common::api::error::IntoHttpError> {
135            use http::header;
136
137            let query_string = serde_html_form::to_string(RequestQuery { format: self.format })?;
138
139            http::Request::builder()
140                .method(http::Method::GET)
141                .uri(METADATA.make_endpoint_url(
142                    considering,
143                    base_url,
144                    &[&self.room_id, &self.event_type, &self.state_key],
145                    &query_string,
146                )?)
147                .header(header::CONTENT_TYPE, "application/json")
148                .header(
149                    header::AUTHORIZATION,
150                    format!(
151                        "Bearer {}",
152                        access_token
153                            .get_required_for_endpoint()
154                            .ok_or(ruma_common::api::error::IntoHttpError::NeedsAuthentication)?,
155                    ),
156                )
157                .body(T::default())
158                .map_err(Into::into)
159        }
160    }
161
162    #[cfg(feature = "server")]
163    impl ruma_common::api::IncomingRequest for Request {
164        type EndpointError = crate::Error;
165        type OutgoingResponse = Response;
166
167        const METADATA: Metadata = METADATA;
168
169        fn try_from_http_request<B, S>(
170            request: http::Request<B>,
171            path_args: &[S],
172        ) -> Result<Self, ruma_common::api::error::FromHttpRequestError>
173        where
174            B: AsRef<[u8]>,
175            S: AsRef<str>,
176        {
177            // FIXME: find a way to make this if-else collapse with serde recognizing trailing
178            // Option
179            let (room_id, event_type, state_key): (OwnedRoomId, StateEventType, String) =
180                if path_args.len() == 3 {
181                    serde::Deserialize::deserialize(serde::de::value::SeqDeserializer::<
182                        _,
183                        serde::de::value::Error,
184                    >::new(
185                        path_args.iter().map(::std::convert::AsRef::as_ref),
186                    ))?
187                } else {
188                    let (a, b) =
189                        serde::Deserialize::deserialize(serde::de::value::SeqDeserializer::<
190                            _,
191                            serde::de::value::Error,
192                        >::new(
193                            path_args.iter().map(::std::convert::AsRef::as_ref),
194                        ))?;
195
196                    (a, b, "".into())
197                };
198
199            let RequestQuery { format } =
200                serde_html_form::from_str(request.uri().query().unwrap_or(""))?;
201
202            Ok(Self { room_id, event_type, state_key, format })
203        }
204    }
205
206    /// Data in the request's query string.
207    #[derive(Debug)]
208    #[cfg_attr(feature = "client", derive(serde::Serialize))]
209    #[cfg_attr(feature = "server", derive(serde::Deserialize))]
210    struct RequestQuery {
211        /// Timestamp to use for the `origin_server_ts` of the event.
212        #[serde(default, skip_serializing_if = "ruma_common::serde::is_default")]
213        format: StateEventFormat,
214    }
215}
216
217#[cfg(all(test, feature = "client"))]
218mod tests {
219    use ruma_common::api::IncomingResponse;
220    use ruma_events::room::name::RoomNameEventContent;
221    use serde_json::{json, to_vec as to_json_vec};
222
223    use super::v3::Response;
224
225    #[test]
226    fn deserialize_response() {
227        let body = json!({
228            "name": "Nice room 🙂"
229        });
230        let response = http::Response::new(to_json_vec(&body).unwrap());
231
232        let response = Response::try_from_http_response(response).unwrap();
233        let content =
234            response.into_content().deserialize_as_unchecked::<RoomNameEventContent>().unwrap();
235
236        assert_eq!(&content.name, "Nice room 🙂");
237    }
238}