1use as_variant::as_variant;
6use ruma_common::{
7 OwnedDeviceId, OwnedTransactionId,
8 serde::{JsonObject, StringEnum},
9};
10use ruma_macros::EventContent;
11use serde::{Deserialize, Serialize, de};
12use serde_json::{Value as JsonValue, from_value as from_json_value};
13
14use crate::{GlobalAccountDataEventType, PrivOwnedStr};
15
16#[derive(Clone, Debug, Serialize, Deserialize, EventContent)]
22#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
23#[ruma_event(type = "m.secret.request", kind = ToDevice)]
24pub struct ToDeviceSecretRequestEventContent {
25 #[serde(flatten)]
27 pub action: RequestAction,
28
29 pub requesting_device_id: OwnedDeviceId,
31
32 pub request_id: OwnedTransactionId,
38}
39
40impl ToDeviceSecretRequestEventContent {
41 pub fn new(
44 action: RequestAction,
45 requesting_device_id: OwnedDeviceId,
46 request_id: OwnedTransactionId,
47 ) -> Self {
48 Self { action, requesting_device_id, request_id }
49 }
50}
51
52#[derive(Clone, Debug, Serialize)]
54#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
55#[serde(tag = "action", rename_all = "snake_case")]
56pub enum RequestAction {
57 Request(SecretRequestAction),
59
60 RequestCancellation,
62
63 #[doc(hidden)]
64 #[serde(untagged)]
65 _Custom(CustomRequestAction),
66}
67
68impl RequestAction {
69 pub fn action(&self) -> &str {
71 match self {
72 Self::Request(_) => "request",
73 Self::RequestCancellation => "request_cancellation",
74 Self::_Custom(custom) => &custom.action,
75 }
76 }
77}
78
79impl<'de> Deserialize<'de> for RequestAction {
80 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
81 where
82 D: serde::Deserializer<'de>,
83 {
84 let mut json = JsonObject::deserialize(deserializer)?;
85
86 let action = json
87 .remove("action")
88 .and_then(|value| as_variant!(value, JsonValue::String))
89 .ok_or_else(|| de::Error::missing_field("action"))?;
90
91 match action.as_ref() {
92 "request" => from_json_value(json.into()).map(Self::Request),
93 "request_cancellation" => Ok(Self::RequestCancellation),
94 _ => Ok(Self::_Custom(CustomRequestAction { action })),
95 }
96 .map_err(de::Error::custom)
97 }
98}
99
100impl From<SecretRequestAction> for RequestAction {
101 fn from(value: SecretRequestAction) -> Self {
102 Self::Request(value)
103 }
104}
105
106#[derive(Clone, Debug, Serialize, Deserialize)]
108#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
109pub struct SecretRequestAction {
110 pub name: SecretName,
112}
113
114impl SecretRequestAction {
115 pub fn new(name: SecretName) -> Self {
117 Self { name }
118 }
119}
120
121#[doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/src/doc/string_enum.md"))]
123#[derive(Clone, StringEnum)]
124#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
125pub enum SecretName {
126 #[ruma_enum(rename = "m.cross_signing.master")]
128 CrossSigningMasterKey,
129
130 #[ruma_enum(rename = "m.cross_signing.user_signing")]
132 CrossSigningUserSigningKey,
133
134 #[ruma_enum(rename = "m.cross_signing.self_signing")]
136 CrossSigningSelfSigningKey,
137
138 #[ruma_enum(rename = "m.megolm_backup.v1")]
140 RecoveryKey,
141
142 #[doc(hidden)]
143 _Custom(PrivOwnedStr),
144}
145
146impl From<SecretName> for GlobalAccountDataEventType {
147 fn from(value: SecretName) -> Self {
148 GlobalAccountDataEventType::from(value.as_str())
149 }
150}
151
152#[doc(hidden)]
154#[derive(Clone, Debug, Serialize)]
155pub struct CustomRequestAction {
156 action: String,
158}
159
160#[cfg(test)]
161mod tests {
162 use assert_matches::assert_matches;
163 use ruma_common::canonical_json::assert_to_canonical_json_eq;
164 use serde_json::{from_value as from_json_value, json};
165 use strass::assert_let;
166
167 use super::{
168 RequestAction, SecretName, SecretRequestAction, ToDeviceSecretRequestEventContent,
169 };
170
171 #[test]
172 fn secret_request_serialization() {
173 let content = ToDeviceSecretRequestEventContent::new(
174 RequestAction::Request(SecretRequestAction::new("org.example.some.secret".into())),
175 "ABCDEFG".into(),
176 "randomly_generated_id_9573".into(),
177 );
178
179 assert_to_canonical_json_eq!(
180 content,
181 json!({
182 "name": "org.example.some.secret",
183 "action": "request",
184 "requesting_device_id": "ABCDEFG",
185 "request_id": "randomly_generated_id_9573",
186 }),
187 );
188 }
189
190 #[test]
191 fn secret_request_recovery_key_serialization() {
192 let content = ToDeviceSecretRequestEventContent::new(
193 RequestAction::Request(SecretRequestAction::new(SecretName::RecoveryKey)),
194 "XYZxyz".into(),
195 "this_is_a_request_id".into(),
196 );
197
198 assert_to_canonical_json_eq!(
199 content,
200 json!({
201 "name": "m.megolm_backup.v1",
202 "action": "request",
203 "requesting_device_id": "XYZxyz",
204 "request_id": "this_is_a_request_id",
205 }),
206 );
207 }
208
209 #[test]
210 fn secret_request_cancellation_serialization() {
211 let content = ToDeviceSecretRequestEventContent::new(
212 RequestAction::RequestCancellation,
213 "ABCDEFG".into(),
214 "randomly_generated_id_9573".into(),
215 );
216
217 assert_to_canonical_json_eq!(
218 content,
219 json!({
220 "action": "request_cancellation",
221 "requesting_device_id": "ABCDEFG",
222 "request_id": "randomly_generated_id_9573",
223 }),
224 );
225 }
226
227 #[test]
228 fn secret_request_deserialization() {
229 let json = json!({
230 "name": "org.example.some.secret",
231 "action": "request",
232 "requesting_device_id": "ABCDEFG",
233 "request_id": "randomly_generated_id_9573"
234 });
235
236 let content = from_json_value::<ToDeviceSecretRequestEventContent>(json).unwrap();
237 assert_eq!(content.requesting_device_id, "ABCDEFG");
238 assert_eq!(content.request_id, "randomly_generated_id_9573");
239 assert_let!(RequestAction::Request(secret) = content.action);
240 assert_eq!(secret.name.as_str(), "org.example.some.secret");
241 }
242
243 #[test]
244 fn secret_request_cancellation_deserialization() {
245 let json = json!({
246 "action": "request_cancellation",
247 "requesting_device_id": "ABCDEFG",
248 "request_id": "randomly_generated_id_9573"
249 });
250
251 let content = from_json_value::<ToDeviceSecretRequestEventContent>(json).unwrap();
252 assert_eq!(content.requesting_device_id, "ABCDEFG");
253 assert_eq!(content.request_id, "randomly_generated_id_9573");
254 assert_matches!(content.action, RequestAction::RequestCancellation);
255 }
256
257 #[test]
258 fn secret_request_recovery_key_deserialization() {
259 let json = json!({
260 "name": "m.megolm_backup.v1",
261 "action": "request",
262 "requesting_device_id": "XYZxyz",
263 "request_id": "this_is_a_request_id"
264 });
265
266 let content = from_json_value::<ToDeviceSecretRequestEventContent>(json).unwrap();
267 assert_eq!(content.requesting_device_id, "XYZxyz");
268 assert_eq!(content.request_id, "this_is_a_request_id");
269 assert_let!(RequestAction::Request(secret) = content.action);
270 assert_eq!(secret.name, SecretName::RecoveryKey);
271 }
272
273 #[test]
274 fn secret_custom_action_serialization_roundtrip() {
275 let json = json!({
276 "action": "my_custom_action",
277 "requesting_device_id": "XYZxyz",
278 "request_id": "this_is_a_request_id"
279 });
280
281 let content = from_json_value::<ToDeviceSecretRequestEventContent>(json.clone()).unwrap();
282 assert_eq!(content.requesting_device_id, "XYZxyz");
283 assert_eq!(content.request_id, "this_is_a_request_id");
284 assert_eq!(content.action.action(), "my_custom_action");
285
286 assert_to_canonical_json_eq!(content, json);
287 }
288}