1use std::{
6 collections::BTreeMap,
7 hash::{Hash, Hasher},
8};
9
10use serde::{Deserialize, Serialize};
11
12use crate::{
13 serde::StringEnum, MilliSecondsSinceUnixEpoch, OwnedRoomAliasId, OwnedUserId, PrivOwnedStr,
14};
15
16#[derive(Clone, Debug, Deserialize, Serialize)]
21#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
22pub struct Protocol {
23 pub user_fields: Vec<String>,
25
26 pub location_fields: Vec<String>,
28
29 #[cfg_attr(feature = "compat-optional", serde(default))]
34 pub icon: String,
35
36 pub field_types: BTreeMap<String, FieldType>,
38
39 pub instances: Vec<ProtocolInstance>,
41}
42
43#[derive(Debug)]
48#[allow(clippy::exhaustive_structs)]
49pub struct ProtocolInit {
50 pub user_fields: Vec<String>,
52
53 pub location_fields: Vec<String>,
55
56 pub icon: String,
58
59 pub field_types: BTreeMap<String, FieldType>,
61
62 pub instances: Vec<ProtocolInstance>,
64}
65
66impl From<ProtocolInit> for Protocol {
67 fn from(init: ProtocolInit) -> Self {
68 let ProtocolInit { user_fields, location_fields, icon, field_types, instances } = init;
69 Self { user_fields, location_fields, icon, field_types, instances }
70 }
71}
72
73#[derive(Clone, Debug, Deserialize, Serialize)]
78#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
79pub struct ProtocolInstance {
80 pub desc: String,
82
83 #[serde(skip_serializing_if = "Option::is_none")]
85 pub icon: Option<String>,
86
87 pub fields: BTreeMap<String, String>,
89
90 pub network_id: String,
92
93 #[cfg(feature = "unstable-unspecified")]
97 #[serde(skip_serializing_if = "Option::is_none")]
98 pub instance_id: Option<String>,
99}
100
101#[derive(Debug)]
106#[allow(clippy::exhaustive_structs)]
107pub struct ProtocolInstanceInit {
108 pub desc: String,
110
111 pub fields: BTreeMap<String, String>,
113
114 pub network_id: String,
116}
117
118impl From<ProtocolInstanceInit> for ProtocolInstance {
119 fn from(init: ProtocolInstanceInit) -> Self {
120 let ProtocolInstanceInit { desc, fields, network_id } = init;
121 Self {
122 desc,
123 icon: None,
124 fields,
125 network_id,
126 #[cfg(feature = "unstable-unspecified")]
127 instance_id: None,
128 }
129 }
130}
131
132#[derive(Clone, Debug, Deserialize, Serialize)]
137#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
138pub struct FieldType {
139 pub regexp: String,
141
142 pub placeholder: String,
144}
145
146#[derive(Debug)]
151#[allow(clippy::exhaustive_structs)]
152pub struct FieldTypeInit {
153 pub regexp: String,
155
156 pub placeholder: String,
158}
159
160impl From<FieldTypeInit> for FieldType {
161 fn from(init: FieldTypeInit) -> Self {
162 let FieldTypeInit { regexp, placeholder } = init;
163 Self { regexp, placeholder }
164 }
165}
166
167#[derive(Clone, Debug, Deserialize, Serialize)]
169#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
170pub struct Location {
171 pub alias: OwnedRoomAliasId,
173
174 pub protocol: String,
176
177 pub fields: BTreeMap<String, String>,
179}
180
181impl Location {
182 pub fn new(
184 alias: OwnedRoomAliasId,
185 protocol: String,
186 fields: BTreeMap<String, String>,
187 ) -> Self {
188 Self { alias, protocol, fields }
189 }
190}
191
192#[derive(Clone, Debug, Deserialize, Serialize)]
194#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
195pub struct User {
196 pub userid: OwnedUserId,
198
199 pub protocol: String,
201
202 pub fields: BTreeMap<String, String>,
204}
205
206impl User {
207 pub fn new(userid: OwnedUserId, protocol: String, fields: BTreeMap<String, String>) -> Self {
209 Self { userid, protocol, fields }
210 }
211}
212
213#[doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/src/doc/string_enum.md"))]
215#[derive(Clone, PartialEq, Eq, StringEnum)]
216#[ruma_enum(rename_all = "lowercase")]
217#[non_exhaustive]
218pub enum Medium {
219 Email,
221
222 Msisdn,
224
225 #[doc(hidden)]
226 _Custom(PrivOwnedStr),
227}
228
229#[derive(Clone, Debug, Deserialize, Serialize)]
234#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
235pub struct ThirdPartyIdentifier {
236 pub address: String,
238
239 pub medium: Medium,
241
242 pub validated_at: MilliSecondsSinceUnixEpoch,
244
245 pub added_at: MilliSecondsSinceUnixEpoch,
247}
248
249impl Eq for ThirdPartyIdentifier {}
250
251impl Hash for ThirdPartyIdentifier {
252 fn hash<H: Hasher>(&self, hasher: &mut H) {
253 (self.medium.as_str(), &self.address).hash(hasher);
254 }
255}
256
257impl PartialEq for ThirdPartyIdentifier {
258 fn eq(&self, other: &ThirdPartyIdentifier) -> bool {
259 self.address == other.address && self.medium == other.medium
260 }
261}
262
263#[derive(Debug)]
268#[allow(clippy::exhaustive_structs)]
269pub struct ThirdPartyIdentifierInit {
270 pub address: String,
272
273 pub medium: Medium,
275
276 pub validated_at: MilliSecondsSinceUnixEpoch,
278
279 pub added_at: MilliSecondsSinceUnixEpoch,
281}
282
283impl From<ThirdPartyIdentifierInit> for ThirdPartyIdentifier {
284 fn from(init: ThirdPartyIdentifierInit) -> Self {
285 let ThirdPartyIdentifierInit { address, medium, validated_at, added_at } = init;
286 ThirdPartyIdentifier { address, medium, validated_at, added_at }
287 }
288}
289
290#[cfg(test)]
291mod tests {
292 use serde_json::{from_value as from_json_value, json, to_value as to_json_value};
293
294 use super::{Medium, ThirdPartyIdentifier};
295 use crate::MilliSecondsSinceUnixEpoch;
296
297 #[test]
298 fn third_party_identifier_serde() {
299 let third_party_id = ThirdPartyIdentifier {
300 address: "monkey@banana.island".into(),
301 medium: Medium::Email,
302 validated_at: MilliSecondsSinceUnixEpoch(1_535_176_800_000_u64.try_into().unwrap()),
303 added_at: MilliSecondsSinceUnixEpoch(1_535_336_848_756_u64.try_into().unwrap()),
304 };
305
306 let third_party_id_serialized = json!({
307 "medium": "email",
308 "address": "monkey@banana.island",
309 "validated_at": 1_535_176_800_000_u64,
310 "added_at": 1_535_336_848_756_u64
311 });
312
313 assert_eq!(to_json_value(third_party_id.clone()).unwrap(), third_party_id_serialized);
314 assert_eq!(third_party_id, from_json_value(third_party_id_serialized).unwrap());
315 }
316}