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_matches2::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
166 use super::{
167 RequestAction, SecretName, SecretRequestAction, ToDeviceSecretRequestEventContent,
168 };
169
170 #[test]
171 fn secret_request_serialization() {
172 let content = ToDeviceSecretRequestEventContent::new(
173 RequestAction::Request(SecretRequestAction::new("org.example.some.secret".into())),
174 "ABCDEFG".into(),
175 "randomly_generated_id_9573".into(),
176 );
177
178 assert_to_canonical_json_eq!(
179 content,
180 json!({
181 "name": "org.example.some.secret",
182 "action": "request",
183 "requesting_device_id": "ABCDEFG",
184 "request_id": "randomly_generated_id_9573",
185 }),
186 );
187 }
188
189 #[test]
190 fn secret_request_recovery_key_serialization() {
191 let content = ToDeviceSecretRequestEventContent::new(
192 RequestAction::Request(SecretRequestAction::new(SecretName::RecoveryKey)),
193 "XYZxyz".into(),
194 "this_is_a_request_id".into(),
195 );
196
197 assert_to_canonical_json_eq!(
198 content,
199 json!({
200 "name": "m.megolm_backup.v1",
201 "action": "request",
202 "requesting_device_id": "XYZxyz",
203 "request_id": "this_is_a_request_id",
204 }),
205 );
206 }
207
208 #[test]
209 fn secret_request_cancellation_serialization() {
210 let content = ToDeviceSecretRequestEventContent::new(
211 RequestAction::RequestCancellation,
212 "ABCDEFG".into(),
213 "randomly_generated_id_9573".into(),
214 );
215
216 assert_to_canonical_json_eq!(
217 content,
218 json!({
219 "action": "request_cancellation",
220 "requesting_device_id": "ABCDEFG",
221 "request_id": "randomly_generated_id_9573",
222 }),
223 );
224 }
225
226 #[test]
227 fn secret_request_deserialization() {
228 let json = json!({
229 "name": "org.example.some.secret",
230 "action": "request",
231 "requesting_device_id": "ABCDEFG",
232 "request_id": "randomly_generated_id_9573"
233 });
234
235 let content = from_json_value::<ToDeviceSecretRequestEventContent>(json).unwrap();
236 assert_eq!(content.requesting_device_id, "ABCDEFG");
237 assert_eq!(content.request_id, "randomly_generated_id_9573");
238 assert_matches!(content.action, RequestAction::Request(secret));
239 assert_eq!(secret.name.as_str(), "org.example.some.secret");
240 }
241
242 #[test]
243 fn secret_request_cancellation_deserialization() {
244 let json = json!({
245 "action": "request_cancellation",
246 "requesting_device_id": "ABCDEFG",
247 "request_id": "randomly_generated_id_9573"
248 });
249
250 let content = from_json_value::<ToDeviceSecretRequestEventContent>(json).unwrap();
251 assert_eq!(content.requesting_device_id, "ABCDEFG");
252 assert_eq!(content.request_id, "randomly_generated_id_9573");
253 assert_matches!(content.action, RequestAction::RequestCancellation);
254 }
255
256 #[test]
257 fn secret_request_recovery_key_deserialization() {
258 let json = json!({
259 "name": "m.megolm_backup.v1",
260 "action": "request",
261 "requesting_device_id": "XYZxyz",
262 "request_id": "this_is_a_request_id"
263 });
264
265 let content = from_json_value::<ToDeviceSecretRequestEventContent>(json).unwrap();
266 assert_eq!(content.requesting_device_id, "XYZxyz");
267 assert_eq!(content.request_id, "this_is_a_request_id");
268 assert_matches!(content.action, RequestAction::Request(secret));
269 assert_eq!(secret.name, SecretName::RecoveryKey);
270 }
271
272 #[test]
273 fn secret_custom_action_serialization_roundtrip() {
274 let json = json!({
275 "action": "my_custom_action",
276 "requesting_device_id": "XYZxyz",
277 "request_id": "this_is_a_request_id"
278 });
279
280 let content = from_json_value::<ToDeviceSecretRequestEventContent>(json.clone()).unwrap();
281 assert_eq!(content.requesting_device_id, "XYZxyz");
282 assert_eq!(content.request_id, "this_is_a_request_id");
283 assert_eq!(content.action.action(), "my_custom_action");
284
285 assert_to_canonical_json_eq!(content, json);
286 }
287}