ruma_common/
thirdparty.rs

1//! Common types for the [third party networks module][thirdparty].
2//!
3//! [thirdparty]: https://spec.matrix.org/latest/client-server-api/#third-party-networks
4
5use 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/// Metadata about a third party protocol.
17///
18/// To create an instance of this type, first create a `ProtocolInit` and convert it via
19/// `Protocol::from` / `.into()`.
20#[derive(Clone, Debug, Deserialize, Serialize)]
21#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
22pub struct Protocol {
23    /// Fields which may be used to identify a third party user.
24    pub user_fields: Vec<String>,
25
26    /// Fields which may be used to identify a third party location.
27    pub location_fields: Vec<String>,
28
29    /// A content URI representing an icon for the third party protocol.
30    ///
31    /// If the `compat-optional` feature is enabled, this field being absent in JSON will result
32    /// in an empty string instead of an error when deserializing.
33    #[cfg_attr(feature = "compat-optional", serde(default))]
34    pub icon: String,
35
36    /// The type definitions for the fields defined in `user_fields` and `location_fields`.
37    pub field_types: BTreeMap<String, FieldType>,
38
39    /// A list of objects representing independent instances of configuration.
40    pub instances: Vec<ProtocolInstance>,
41}
42
43/// Initial set of fields of `Protocol`.
44///
45/// This struct will not be updated even if additional fields are added to `Prococol` in a new
46/// (non-breaking) release of the Matrix specification.
47#[derive(Debug)]
48#[allow(clippy::exhaustive_structs)]
49pub struct ProtocolInit {
50    /// Fields which may be used to identify a third party user.
51    pub user_fields: Vec<String>,
52
53    /// Fields which may be used to identify a third party location.
54    pub location_fields: Vec<String>,
55
56    /// A content URI representing an icon for the third party protocol.
57    pub icon: String,
58
59    /// The type definitions for the fields defined in `user_fields` and `location_fields`.
60    pub field_types: BTreeMap<String, FieldType>,
61
62    /// A list of objects representing independent instances of configuration.
63    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/// Metadata about an instance of a third party protocol.
74///
75/// To create an instance of this type, first create a `ProtocolInstanceInit` and convert it via
76/// `ProtocolInstance::from` / `.into()`.
77#[derive(Clone, Debug, Deserialize, Serialize)]
78#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
79pub struct ProtocolInstance {
80    /// A human-readable description for the protocol, such as the name.
81    pub desc: String,
82
83    /// An optional content URI representing the protocol.
84    #[serde(skip_serializing_if = "Option::is_none")]
85    pub icon: Option<String>,
86
87    /// Preset values for `fields` the client may use to search by.
88    pub fields: BTreeMap<String, String>,
89
90    /// A unique identifier across all instances.
91    pub network_id: String,
92
93    /// A unique identifier across all instances.
94    ///
95    /// See [matrix-spec#833](https://github.com/matrix-org/matrix-spec/issues/833).
96    #[cfg(feature = "unstable-unspecified")]
97    #[serde(skip_serializing_if = "Option::is_none")]
98    pub instance_id: Option<String>,
99}
100
101/// Initial set of fields of `Protocol`.
102///
103/// This struct will not be updated even if additional fields are added to `Prococol` in a new
104/// (non-breaking) release of the Matrix specification.
105#[derive(Debug)]
106#[allow(clippy::exhaustive_structs)]
107pub struct ProtocolInstanceInit {
108    /// A human-readable description for the protocol, such as the name.
109    pub desc: String,
110
111    /// Preset values for `fields` the client may use to search by.
112    pub fields: BTreeMap<String, String>,
113
114    /// A unique identifier across all instances.
115    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/// A type definition for a field used to identify third party users or locations.
133///
134/// To create an instance of this type, first create a `FieldTypeInit` and convert it via
135/// `FieldType::from` / `.into()`.
136#[derive(Clone, Debug, Deserialize, Serialize)]
137#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
138pub struct FieldType {
139    /// A regular expression for validation of a field's value.
140    pub regexp: String,
141
142    /// A placeholder serving as a valid example of the field value.
143    pub placeholder: String,
144}
145
146/// Initial set of fields of `FieldType`.
147///
148/// This struct will not be updated even if additional fields are added to `FieldType` in a new
149/// (non-breaking) release of the Matrix specification.
150#[derive(Debug)]
151#[allow(clippy::exhaustive_structs)]
152pub struct FieldTypeInit {
153    /// A regular expression for validation of a field's value.
154    pub regexp: String,
155
156    /// A placeholder serving as a valid example of the field value.
157    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/// A third party network location.
168#[derive(Clone, Debug, Deserialize, Serialize)]
169#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
170pub struct Location {
171    /// An alias for a matrix room.
172    pub alias: OwnedRoomAliasId,
173
174    /// The protocol ID that the third party location is a part of.
175    pub protocol: String,
176
177    /// Information used to identify this third party location.
178    pub fields: BTreeMap<String, String>,
179}
180
181impl Location {
182    /// Creates a new `Location` with the given alias, protocol and fields.
183    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/// A third party network user.
193#[derive(Clone, Debug, Deserialize, Serialize)]
194#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
195pub struct User {
196    /// A matrix user ID representing a third party user.
197    pub userid: OwnedUserId,
198
199    /// The protocol ID that the third party user is a part of.
200    pub protocol: String,
201
202    /// Information used to identify this third party user.
203    pub fields: BTreeMap<String, String>,
204}
205
206impl User {
207    /// Creates a new `User` with the given userid, protocol and fields.
208    pub fn new(userid: OwnedUserId, protocol: String, fields: BTreeMap<String, String>) -> Self {
209        Self { userid, protocol, fields }
210    }
211}
212
213/// The medium of a third party identifier.
214#[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 address identifier
220    Email,
221
222    /// Phone number identifier
223    Msisdn,
224
225    #[doc(hidden)]
226    _Custom(PrivOwnedStr),
227}
228
229/// An identifier external to Matrix.
230///
231/// To create an instance of this type, first create a `ThirdPartyIdentifierInit` and convert it to
232/// this type using `ThirdPartyIdentifier::Init` / `.into()`.
233#[derive(Clone, Debug, Deserialize, Serialize)]
234#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
235pub struct ThirdPartyIdentifier {
236    /// The third party identifier address.
237    pub address: String,
238
239    /// The medium of third party identifier.
240    pub medium: Medium,
241
242    /// The time when the identifier was validated by the identity server.
243    pub validated_at: MilliSecondsSinceUnixEpoch,
244
245    /// The time when the homeserver associated the third party identifier with the user.
246    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/// Initial set of fields of `ThirdPartyIdentifier`.
264///
265/// This struct will not be updated even if additional fields are added to `ThirdPartyIdentifier`
266/// in a new (non-breaking) release of the Matrix specification.
267#[derive(Debug)]
268#[allow(clippy::exhaustive_structs)]
269pub struct ThirdPartyIdentifierInit {
270    /// The third party identifier address.
271    pub address: String,
272
273    /// The medium of third party identifier.
274    pub medium: Medium,
275
276    /// The time when the identifier was validated by the identity server.
277    pub validated_at: MilliSecondsSinceUnixEpoch,
278
279    /// The time when the homeserver associated the third party identifier with the user.
280    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}