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