1pub mod v3 {
6 use std::borrow::Borrow;
11
12 use ruma_common::{
13 api::{auth_scheme::AccessToken, response, Metadata},
14 metadata,
15 serde::Raw,
16 MilliSecondsSinceUnixEpoch, OwnedEventId, OwnedRoomId,
17 };
18 use ruma_events::{AnyStateEventContent, StateEventContent, StateEventType};
19 use serde_json::value::to_raw_value as to_raw_json_value;
20
21 metadata! {
22 method: PUT,
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 #[derive(Clone, Debug)]
33 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
34 pub struct Request {
35 pub room_id: OwnedRoomId,
37
38 pub event_type: StateEventType,
40
41 pub state_key: String,
43
44 pub body: Raw<AnyStateEventContent>,
46
47 pub timestamp: Option<MilliSecondsSinceUnixEpoch>,
55 }
56
57 impl Request {
58 pub fn new<T, K>(
65 room_id: OwnedRoomId,
66 state_key: &K,
67 content: &T,
68 ) -> serde_json::Result<Self>
69 where
70 T: StateEventContent,
71 T::StateKey: Borrow<K>,
72 K: AsRef<str> + ?Sized,
73 {
74 Ok(Self {
75 room_id,
76 state_key: state_key.as_ref().to_owned(),
77 event_type: content.event_type(),
78 body: Raw::from_json(to_raw_json_value(content)?),
79 timestamp: None,
80 })
81 }
82
83 pub fn new_raw(
86 room_id: OwnedRoomId,
87 event_type: StateEventType,
88 state_key: String,
89 body: Raw<AnyStateEventContent>,
90 ) -> Self {
91 Self { room_id, event_type, state_key, body, timestamp: None }
92 }
93 }
94
95 #[response(error = crate::Error)]
97 pub struct Response {
98 pub event_id: OwnedEventId,
100 }
101
102 impl Response {
103 pub fn new(event_id: OwnedEventId) -> Self {
105 Self { event_id }
106 }
107 }
108
109 #[cfg(feature = "client")]
110 impl ruma_common::api::OutgoingRequest for Request {
111 type EndpointError = crate::Error;
112 type IncomingResponse = Response;
113
114 fn try_into_http_request<T: Default + bytes::BufMut + AsRef<[u8]>>(
115 self,
116 base_url: &str,
117 access_token: ruma_common::api::auth_scheme::SendAccessToken<'_>,
118 considering: std::borrow::Cow<'_, ruma_common::api::SupportedVersions>,
119 ) -> Result<http::Request<T>, ruma_common::api::error::IntoHttpError> {
120 use ruma_common::api::auth_scheme::AuthScheme;
121
122 let query_string =
123 serde_html_form::to_string(RequestQuery { timestamp: self.timestamp })?;
124
125 let mut http_request = http::Request::builder()
126 .method(Self::METHOD)
127 .uri(Self::make_endpoint_url(
128 considering,
129 base_url,
130 &[&self.room_id, &self.event_type, &self.state_key],
131 &query_string,
132 )?)
133 .header(http::header::CONTENT_TYPE, ruma_common::http_headers::APPLICATION_JSON)
134 .body(ruma_common::serde::json_to_buf(&self.body)?)?;
135
136 Self::Authentication::add_authentication(&mut http_request, access_token).map_err(
137 |error| ruma_common::api::error::IntoHttpError::Authentication(error.into()),
138 )?;
139
140 Ok(http_request)
141 }
142 }
143
144 #[cfg(feature = "server")]
145 impl ruma_common::api::IncomingRequest for Request {
146 type EndpointError = crate::Error;
147 type OutgoingResponse = Response;
148
149 fn try_from_http_request<B, S>(
150 request: http::Request<B>,
151 path_args: &[S],
152 ) -> Result<Self, ruma_common::api::error::FromHttpRequestError>
153 where
154 B: AsRef<[u8]>,
155 S: AsRef<str>,
156 {
157 Self::check_request_method(request.method())?;
158
159 let (room_id, event_type, state_key): (OwnedRoomId, StateEventType, String) =
162 if path_args.len() == 3 {
163 serde::Deserialize::deserialize(serde::de::value::SeqDeserializer::<
164 _,
165 serde::de::value::Error,
166 >::new(
167 path_args.iter().map(::std::convert::AsRef::as_ref),
168 ))?
169 } else {
170 let (a, b) =
171 serde::Deserialize::deserialize(serde::de::value::SeqDeserializer::<
172 _,
173 serde::de::value::Error,
174 >::new(
175 path_args.iter().map(::std::convert::AsRef::as_ref),
176 ))?;
177
178 (a, b, "".into())
179 };
180
181 let request_query: RequestQuery =
182 serde_html_form::from_str(request.uri().query().unwrap_or(""))?;
183
184 let body = serde_json::from_slice(request.body().as_ref())?;
185
186 Ok(Self { room_id, event_type, state_key, body, timestamp: request_query.timestamp })
187 }
188 }
189
190 #[derive(Debug)]
192 #[cfg_attr(feature = "client", derive(serde::Serialize))]
193 #[cfg_attr(feature = "server", derive(serde::Deserialize))]
194 struct RequestQuery {
195 #[serde(rename = "ts", skip_serializing_if = "Option::is_none")]
197 timestamp: Option<MilliSecondsSinceUnixEpoch>,
198 }
199
200 #[cfg(feature = "client")]
201 #[test]
202 fn serialize() {
203 use std::borrow::Cow;
204
205 use ruma_common::{
206 api::{
207 auth_scheme::SendAccessToken, MatrixVersion, OutgoingRequest as _,
208 SupportedVersions,
209 },
210 owned_room_id,
211 };
212 use ruma_events::{room::name::RoomNameEventContent, EmptyStateKey};
213
214 let supported = SupportedVersions {
215 versions: [MatrixVersion::V1_1].into(),
216 features: Default::default(),
217 };
218
219 let req = Request::new(
221 owned_room_id!("!room:server.tld"),
222 &EmptyStateKey,
223 &RoomNameEventContent::new("Test room".to_owned()),
224 )
225 .unwrap()
226 .try_into_http_request::<Vec<u8>>(
227 "https://server.tld",
228 SendAccessToken::IfRequired("access_token"),
229 Cow::Owned(supported),
230 )
231 .unwrap();
232
233 assert_eq!(
234 req.uri(),
235 "https://server.tld/_matrix/client/v3/rooms/!room:server.tld/state/m.room.name/"
236 );
237 }
238}