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