1pub mod v3 {
6 use std::borrow::Borrow;
11
12 use ruma_common::{
13 api::{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>(
115 self,
116 base_url: &str,
117 access_token: ruma_common::api::SendAccessToken<'_>,
118 considering: &'_ ruma_common::api::SupportedVersions,
119 ) -> Result<http::Request<T>, ruma_common::api::error::IntoHttpError> {
120 use http::header::{self, HeaderValue};
121
122 let query_string =
123 serde_html_form::to_string(RequestQuery { timestamp: self.timestamp })?;
124
125 let http_request = http::Request::builder()
126 .method(http::Method::PUT)
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(header::CONTENT_TYPE, "application/json")
134 .header(
135 header::AUTHORIZATION,
136 HeaderValue::from_str(&format!(
137 "Bearer {}",
138 access_token
139 .get_required_for_endpoint()
140 .ok_or(ruma_common::api::error::IntoHttpError::NeedsAuthentication)?
141 ))?,
142 )
143 .body(ruma_common::serde::json_to_buf(&self.body)?)?;
144
145 Ok(http_request)
146 }
147 }
148
149 #[cfg(feature = "server")]
150 impl ruma_common::api::IncomingRequest for Request {
151 type EndpointError = crate::Error;
152 type OutgoingResponse = Response;
153
154 fn try_from_http_request<B, S>(
155 request: http::Request<B>,
156 path_args: &[S],
157 ) -> Result<Self, ruma_common::api::error::FromHttpRequestError>
158 where
159 B: AsRef<[u8]>,
160 S: AsRef<str>,
161 {
162 let (room_id, event_type, state_key): (OwnedRoomId, StateEventType, String) =
165 if path_args.len() == 3 {
166 serde::Deserialize::deserialize(serde::de::value::SeqDeserializer::<
167 _,
168 serde::de::value::Error,
169 >::new(
170 path_args.iter().map(::std::convert::AsRef::as_ref),
171 ))?
172 } else {
173 let (a, b) =
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
181 (a, b, "".into())
182 };
183
184 let request_query: RequestQuery =
185 serde_html_form::from_str(request.uri().query().unwrap_or(""))?;
186
187 let body = serde_json::from_slice(request.body().as_ref())?;
188
189 Ok(Self { room_id, event_type, state_key, body, timestamp: request_query.timestamp })
190 }
191 }
192
193 #[derive(Debug)]
195 #[cfg_attr(feature = "client", derive(serde::Serialize))]
196 #[cfg_attr(feature = "server", derive(serde::Deserialize))]
197 struct RequestQuery {
198 #[serde(rename = "ts", skip_serializing_if = "Option::is_none")]
200 timestamp: Option<MilliSecondsSinceUnixEpoch>,
201 }
202
203 #[cfg(feature = "client")]
204 #[test]
205 fn serialize() {
206 use ruma_common::{
207 api::{MatrixVersion, OutgoingRequest as _, SendAccessToken, SupportedVersions},
208 owned_room_id,
209 };
210 use ruma_events::{room::name::RoomNameEventContent, EmptyStateKey};
211
212 let supported = SupportedVersions {
213 versions: [MatrixVersion::V1_1].into(),
214 features: Default::default(),
215 };
216
217 let req = Request::new(
219 owned_room_id!("!room:server.tld"),
220 &EmptyStateKey,
221 &RoomNameEventContent::new("Test room".to_owned()),
222 )
223 .unwrap()
224 .try_into_http_request::<Vec<u8>>(
225 "https://server.tld",
226 SendAccessToken::IfRequired("access_token"),
227 &supported,
228 )
229 .unwrap();
230
231 assert_eq!(
232 req.uri(),
233 "https://server.tld/_matrix/client/v3/rooms/!room:server.tld/state/m.room.name/"
234 );
235 }
236}