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    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        fn try_into_http_request<T: Default + bytes::BufMut>(
128            self,
129            base_url: &str,
130            access_token: ruma_common::api::SendAccessToken<'_>,
131            considering: &'_ ruma_common::api::SupportedVersions,
132        ) -> Result<http::Request<T>, ruma_common::api::error::IntoHttpError> {
133            use http::header;
134
135            let query_string = serde_html_form::to_string(RequestQuery { format: self.format })?;
136
137            http::Request::builder()
138                .method(http::Method::GET)
139                .uri(Self::make_endpoint_url(
140                    considering,
141                    base_url,
142                    &[&self.room_id, &self.event_type, &self.state_key],
143                    &query_string,
144                )?)
145                .header(header::CONTENT_TYPE, "application/json")
146                .header(
147                    header::AUTHORIZATION,
148                    format!(
149                        "Bearer {}",
150                        access_token
151                            .get_required_for_endpoint()
152                            .ok_or(ruma_common::api::error::IntoHttpError::NeedsAuthentication)?,
153                    ),
154                )
155                .body(T::default())
156                .map_err(Into::into)
157        }
158    }
159
160    #[cfg(feature = "server")]
161    impl ruma_common::api::IncomingRequest for Request {
162        type EndpointError = crate::Error;
163        type OutgoingResponse = Response;
164
165        fn try_from_http_request<B, S>(
166            request: http::Request<B>,
167            path_args: &[S],
168        ) -> Result<Self, ruma_common::api::error::FromHttpRequestError>
169        where
170            B: AsRef<[u8]>,
171            S: AsRef<str>,
172        {
173            // FIXME: find a way to make this if-else collapse with serde recognizing trailing
174            // Option
175            let (room_id, event_type, state_key): (OwnedRoomId, StateEventType, String) =
176                if path_args.len() == 3 {
177                    serde::Deserialize::deserialize(serde::de::value::SeqDeserializer::<
178                        _,
179                        serde::de::value::Error,
180                    >::new(
181                        path_args.iter().map(::std::convert::AsRef::as_ref),
182                    ))?
183                } else {
184                    let (a, b) =
185                        serde::Deserialize::deserialize(serde::de::value::SeqDeserializer::<
186                            _,
187                            serde::de::value::Error,
188                        >::new(
189                            path_args.iter().map(::std::convert::AsRef::as_ref),
190                        ))?;
191
192                    (a, b, "".into())
193                };
194
195            let RequestQuery { format } =
196                serde_html_form::from_str(request.uri().query().unwrap_or(""))?;
197
198            Ok(Self { room_id, event_type, state_key, format })
199        }
200    }
201
202    /// Data in the request's query string.
203    #[derive(Debug)]
204    #[cfg_attr(feature = "client", derive(serde::Serialize))]
205    #[cfg_attr(feature = "server", derive(serde::Deserialize))]
206    struct RequestQuery {
207        /// Timestamp to use for the `origin_server_ts` of the event.
208        #[serde(default, skip_serializing_if = "ruma_common::serde::is_default")]
209        format: StateEventFormat,
210    }
211}
212
213#[cfg(all(test, feature = "client"))]
214mod tests {
215    use ruma_common::api::IncomingResponse;
216    use ruma_events::room::name::RoomNameEventContent;
217    use serde_json::{json, to_vec as to_json_vec};
218
219    use super::v3::Response;
220
221    #[test]
222    fn deserialize_response() {
223        let body = json!({
224            "name": "Nice room 🙂"
225        });
226        let response = http::Response::new(to_json_vec(&body).unwrap());
227
228        let response = Response::try_from_http_response(response).unwrap();
229        let content =
230            response.into_content().deserialize_as_unchecked::<RoomNameEventContent>().unwrap();
231
232        assert_eq!(&content.name, "Nice room 🙂");
233    }
234}