Skip to main content

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