use std::{
collections::BTreeMap,
hash::{Hash, Hasher},
};
use serde::{Deserialize, Serialize};
use crate::{
MilliSecondsSinceUnixEpoch, OwnedRoomAliasId, OwnedUserId, PrivOwnedStr, serde::StringEnum,
};
#[derive(Clone, Debug, Deserialize, Serialize)]
#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
pub struct Protocol<I = ProtocolInstance> {
pub user_fields: Vec<String>,
pub location_fields: Vec<String>,
#[cfg_attr(feature = "compat-optional", serde(default))]
pub icon: String,
pub field_types: BTreeMap<String, FieldType>,
pub instances: Vec<I>,
}
impl<I> Protocol<I> {
pub fn into<J: From<I>>(self) -> Protocol<J> {
let Self { user_fields, location_fields, icon, field_types, instances } = self;
Protocol {
user_fields,
location_fields,
icon,
field_types,
instances: instances.into_iter().map(J::from).collect(),
}
}
}
#[derive(Debug)]
#[allow(clippy::exhaustive_structs)]
pub struct ProtocolInit<I = ProtocolInstance> {
pub user_fields: Vec<String>,
pub location_fields: Vec<String>,
pub icon: String,
pub field_types: BTreeMap<String, FieldType>,
pub instances: Vec<I>,
}
impl<I> From<ProtocolInit<I>> for Protocol<I> {
fn from(init: ProtocolInit<I>) -> Self {
let ProtocolInit { user_fields, location_fields, icon, field_types, instances } = init;
Self { user_fields, location_fields, icon, field_types, instances }
}
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
pub struct ProtocolInstance {
pub desc: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub icon: Option<String>,
pub fields: BTreeMap<String, String>,
pub network_id: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub instance_id: Option<String>,
}
#[derive(Debug)]
#[allow(clippy::exhaustive_structs)]
pub struct ProtocolInstanceInit {
pub desc: String,
pub fields: BTreeMap<String, String>,
pub network_id: String,
}
impl From<ProtocolInstanceInit> for ProtocolInstance {
fn from(init: ProtocolInstanceInit) -> Self {
let ProtocolInstanceInit { desc, fields, network_id } = init;
Self { desc, icon: None, fields, network_id, instance_id: None }
}
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
pub struct FieldType {
pub regexp: String,
pub placeholder: String,
}
#[derive(Debug)]
#[allow(clippy::exhaustive_structs)]
pub struct FieldTypeInit {
pub regexp: String,
pub placeholder: String,
}
impl From<FieldTypeInit> for FieldType {
fn from(init: FieldTypeInit) -> Self {
let FieldTypeInit { regexp, placeholder } = init;
Self { regexp, placeholder }
}
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
pub struct Location {
pub alias: OwnedRoomAliasId,
pub protocol: String,
pub fields: BTreeMap<String, String>,
}
impl Location {
pub fn new(
alias: OwnedRoomAliasId,
protocol: String,
fields: BTreeMap<String, String>,
) -> Self {
Self { alias, protocol, fields }
}
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
pub struct User {
pub userid: OwnedUserId,
pub protocol: String,
pub fields: BTreeMap<String, String>,
}
impl User {
pub fn new(userid: OwnedUserId, protocol: String, fields: BTreeMap<String, String>) -> Self {
Self { userid, protocol, fields }
}
}
#[doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/src/doc/string_enum.md"))]
#[derive(Clone, StringEnum)]
#[ruma_enum(rename_all = "lowercase")]
#[non_exhaustive]
pub enum Medium {
Email,
Msisdn,
#[doc(hidden)]
_Custom(PrivOwnedStr),
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
pub struct ThirdPartyIdentifier {
pub address: String,
pub medium: Medium,
pub validated_at: MilliSecondsSinceUnixEpoch,
pub added_at: MilliSecondsSinceUnixEpoch,
}
impl Eq for ThirdPartyIdentifier {}
impl Hash for ThirdPartyIdentifier {
fn hash<H: Hasher>(&self, hasher: &mut H) {
(self.medium.as_str(), &self.address).hash(hasher);
}
}
impl PartialEq for ThirdPartyIdentifier {
fn eq(&self, other: &ThirdPartyIdentifier) -> bool {
self.address == other.address && self.medium == other.medium
}
}
#[derive(Debug)]
#[allow(clippy::exhaustive_structs)]
pub struct ThirdPartyIdentifierInit {
pub address: String,
pub medium: Medium,
pub validated_at: MilliSecondsSinceUnixEpoch,
pub added_at: MilliSecondsSinceUnixEpoch,
}
impl From<ThirdPartyIdentifierInit> for ThirdPartyIdentifier {
fn from(init: ThirdPartyIdentifierInit) -> Self {
let ThirdPartyIdentifierInit { address, medium, validated_at, added_at } = init;
ThirdPartyIdentifier { address, medium, validated_at, added_at }
}
}
#[cfg(test)]
mod tests {
use serde_json::{from_value as from_json_value, json, to_value as to_json_value};
use super::{Medium, ThirdPartyIdentifier};
use crate::MilliSecondsSinceUnixEpoch;
#[test]
fn third_party_identifier_serde() {
let third_party_id = ThirdPartyIdentifier {
address: "monkey@banana.island".into(),
medium: Medium::Email,
validated_at: MilliSecondsSinceUnixEpoch(1_535_176_800_000_u64.try_into().unwrap()),
added_at: MilliSecondsSinceUnixEpoch(1_535_336_848_756_u64.try_into().unwrap()),
};
let third_party_id_serialized = json!({
"medium": "email",
"address": "monkey@banana.island",
"validated_at": 1_535_176_800_000_u64,
"added_at": 1_535_336_848_756_u64
});
assert_eq!(to_json_value(third_party_id.clone()).unwrap(), third_party_id_serialized);
assert_eq!(third_party_id, from_json_value(third_party_id_serialized).unwrap());
}
}