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