use std::borrow::Cow;
use ruma_macros::StringEnum;
#[cfg(feature = "unstable-msc4426")]
use serde::Deserialize;
use serde::Serialize;
use serde_json::{Value as JsonValue, from_value as from_json_value, to_value as to_json_value};
#[cfg(feature = "unstable-msc4426")]
use crate::SecondsSinceUnixEpoch;
use crate::{OwnedMxcUri, PrivOwnedStr};
mod profile_field_value_serde;
mod static_profile_field;
mod user_profile;
#[cfg(feature = "unstable-msc4262")]
mod user_profile_update;
#[doc(hidden)]
pub use self::profile_field_value_serde::ProfileFieldValueVisitor;
#[cfg(feature = "unstable-msc4262")]
pub use self::user_profile_update::*;
pub use self::{static_profile_field::*, user_profile::*};
#[doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/src/doc/string_enum.md"))]
#[derive(Clone, StringEnum)]
#[ruma_enum(rename_all = "snake_case")]
#[non_exhaustive]
pub enum ProfileFieldName {
AvatarUrl,
#[ruma_enum(rename = "displayname")]
DisplayName,
#[ruma_enum(rename = "m.tz")]
TimeZone,
#[cfg(feature = "unstable-msc4426")]
#[ruma_enum(rename = "org.matrix.msc4426.status")]
Status,
#[cfg(feature = "unstable-msc4426")]
#[ruma_enum(rename = "org.matrix.msc4426.call")]
Call,
#[doc(hidden)]
_Custom(PrivOwnedStr),
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
#[serde(rename_all = "snake_case")]
#[non_exhaustive]
pub enum ProfileFieldValue {
AvatarUrl(OwnedMxcUri),
#[serde(rename = "displayname")]
DisplayName(String),
#[serde(rename = "m.tz")]
TimeZone(String),
#[cfg(feature = "unstable-msc4426")]
#[serde(rename = "org.matrix.msc4426.status")]
Status(StatusProfileField),
#[cfg(feature = "unstable-msc4426")]
#[serde(rename = "org.matrix.msc4426.call")]
Call(CallProfileField),
#[doc(hidden)]
#[serde(untagged)]
_Custom(CustomProfileFieldValue),
}
impl ProfileFieldValue {
pub fn new(field: &str, value: JsonValue) -> serde_json::Result<Self> {
Ok(match field {
"avatar_url" => Self::AvatarUrl(from_json_value(value)?),
"displayname" => Self::DisplayName(from_json_value(value)?),
"m.tz" => Self::TimeZone(from_json_value(value)?),
_ => Self::_Custom(CustomProfileFieldValue { field: field.to_owned(), value }),
})
}
pub fn field_name(&self) -> ProfileFieldName {
match self {
Self::AvatarUrl(_) => ProfileFieldName::AvatarUrl,
Self::DisplayName(_) => ProfileFieldName::DisplayName,
Self::TimeZone(_) => ProfileFieldName::TimeZone,
#[cfg(feature = "unstable-msc4426")]
Self::Status(_) => ProfileFieldName::Status,
#[cfg(feature = "unstable-msc4426")]
Self::Call(_) => ProfileFieldName::Call,
Self::_Custom(CustomProfileFieldValue { field, .. }) => field.as_str().into(),
}
}
pub fn value(&self) -> Cow<'_, JsonValue> {
match self {
Self::AvatarUrl(value) => {
Cow::Owned(to_json_value(value).expect("value should serialize successfully"))
}
Self::DisplayName(value) => {
Cow::Owned(to_json_value(value).expect("value should serialize successfully"))
}
Self::TimeZone(value) => {
Cow::Owned(to_json_value(value).expect("value should serialize successfully"))
}
#[cfg(feature = "unstable-msc4426")]
Self::Status(value) => {
Cow::Owned(to_json_value(value).expect("value should serialize successfully"))
}
#[cfg(feature = "unstable-msc4426")]
Self::Call(value) => {
Cow::Owned(to_json_value(value).expect("value should serialize successfully"))
}
Self::_Custom(c) => Cow::Borrowed(&c.value),
}
}
}
#[cfg(feature = "unstable-msc4426")]
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[non_exhaustive]
pub struct StatusProfileField {
pub text: String,
pub emoji: String,
}
#[cfg(feature = "unstable-msc4426")]
impl StatusProfileField {
pub fn new(text: String, emoji: String) -> Self {
Self { text, emoji }
}
}
#[cfg(feature = "unstable-msc4426")]
#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[non_exhaustive]
pub struct CallProfileField {
#[serde(skip_serializing_if = "Option::is_none")]
pub call_joined_ts: Option<SecondsSinceUnixEpoch>,
}
#[cfg(feature = "unstable-msc4426")]
impl CallProfileField {
pub fn new() -> Self {
Self::default()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[doc(hidden)]
pub struct CustomProfileFieldValue {
field: String,
value: JsonValue,
}
#[cfg(test)]
mod tests {
use ruma_common::{canonical_json::assert_to_canonical_json_eq, owned_mxc_uri};
use serde_json::{from_value as from_json_value, json};
use super::ProfileFieldValue;
#[cfg(feature = "unstable-msc4426")]
use super::{CallProfileField, StatusProfileField};
#[cfg(feature = "unstable-msc4426")]
use crate::SecondsSinceUnixEpoch;
#[test]
fn serialize_profile_field_value() {
let value = ProfileFieldValue::AvatarUrl(owned_mxc_uri!("mxc://localhost/abcdef"));
assert_to_canonical_json_eq!(value, json!({ "avatar_url": "mxc://localhost/abcdef" }));
let value = ProfileFieldValue::DisplayName("Alice".to_owned());
assert_to_canonical_json_eq!(value, json!({ "displayname": "Alice" }));
let value = ProfileFieldValue::new("custom_field", "value".into()).unwrap();
assert_to_canonical_json_eq!(value, json!({ "custom_field": "value" }));
}
#[test]
fn deserialize_profile_field_value() {
let json = json!({ "avatar_url": "mxc://localhost/abcdef" });
assert_eq!(
from_json_value::<ProfileFieldValue>(json).unwrap(),
ProfileFieldValue::AvatarUrl(owned_mxc_uri!("mxc://localhost/abcdef"))
);
let json = json!({ "displayname": "Alice" });
assert_eq!(
from_json_value::<ProfileFieldValue>(json).unwrap(),
ProfileFieldValue::DisplayName("Alice".to_owned())
);
let json = json!({ "custom_field": "value" });
let value = from_json_value::<ProfileFieldValue>(json).unwrap();
assert_eq!(value.field_name().as_str(), "custom_field");
assert_eq!(value.value().as_str(), Some("value"));
let json = json!({});
from_json_value::<ProfileFieldValue>(json).unwrap_err();
}
#[test]
#[cfg(feature = "unstable-msc4426")]
fn serialize_profile_status() {
let value =
ProfileFieldValue::Status(StatusProfileField::new("Away".to_owned(), "🌴".to_owned()));
assert_to_canonical_json_eq!(
value,
json!({ "org.matrix.msc4426.status": { "text": "Away", "emoji": "🌴" } })
);
let mut call = CallProfileField::new();
call.call_joined_ts = Some(SecondsSinceUnixEpoch(1_770_140_640.try_into().unwrap()));
let value = ProfileFieldValue::Call(call);
assert_to_canonical_json_eq!(
value,
json!({ "org.matrix.msc4426.call": { "call_joined_ts": 1_770_140_640 } })
);
}
#[test]
#[cfg(feature = "unstable-msc4426")]
fn deserialize_profile_status() {
let json =
json!({ "org.matrix.msc4426.status": { "text": "Be right back", "emoji": "☕️" } });
assert_eq!(
from_json_value::<ProfileFieldValue>(json).unwrap(),
ProfileFieldValue::Status(StatusProfileField::new(
"Be right back".to_owned(),
"☕️".to_owned(),
))
);
let json = json!({ "org.matrix.msc4426.call": { "call_joined_ts": 1_168_380_060 } });
let mut expected_call = CallProfileField::new();
expected_call.call_joined_ts =
Some(SecondsSinceUnixEpoch(1_168_380_060.try_into().unwrap()));
assert_eq!(
from_json_value::<ProfileFieldValue>(json).unwrap(),
ProfileFieldValue::Call(expected_call)
);
}
}