use std::collections::{BTreeMap, btree_map};
use serde::{Deserialize, Serialize};
use serde_json::Value as JsonValue;
#[cfg(feature = "unstable-msc4262")]
use super::UserProfileChanges;
use super::{ProfileFieldName, ProfileFieldValue, static_profile_field::StaticProfileField};
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
#[serde(transparent)]
pub struct UserProfile(BTreeMap<String, JsonValue>);
impl UserProfile {
pub fn new() -> Self {
Self::default()
}
pub fn get(&self, field: &str) -> Option<&JsonValue> {
self.0.get(field)
}
pub fn get_static<F: StaticProfileField>(&self) -> Result<Option<F::Value>, serde_json::Error> {
self.0.get(F::NAME).map(|value| serde_json::from_value(value.clone())).transpose()
}
pub fn iter(&self) -> btree_map::Iter<'_, String, JsonValue> {
self.0.iter()
}
pub fn set(&mut self, field: String, value: JsonValue) {
self.0.insert(field, value);
}
#[cfg(feature = "unstable-msc4262")]
pub fn apply(&mut self, changes: UserProfileChanges) {
for (field, value) in changes.updated {
self.0.insert(field.to_string(), value);
}
for field in changes.removed {
self.0.remove(field.as_str());
}
}
}
impl FromIterator<(String, JsonValue)> for UserProfile {
fn from_iter<T: IntoIterator<Item = (String, JsonValue)>>(iter: T) -> Self {
Self(iter.into_iter().collect())
}
}
impl FromIterator<(ProfileFieldName, JsonValue)> for UserProfile {
fn from_iter<T: IntoIterator<Item = (ProfileFieldName, JsonValue)>>(iter: T) -> Self {
iter.into_iter().map(|(field, value)| (field.as_str().to_owned(), value)).collect()
}
}
impl FromIterator<ProfileFieldValue> for UserProfile {
fn from_iter<T: IntoIterator<Item = ProfileFieldValue>>(iter: T) -> Self {
iter.into_iter().map(|value| (value.field_name(), value.value().into_owned())).collect()
}
}
impl Extend<(String, JsonValue)> for UserProfile {
fn extend<T: IntoIterator<Item = (String, JsonValue)>>(&mut self, iter: T) {
self.0.extend(iter);
}
}
impl Extend<(ProfileFieldName, JsonValue)> for UserProfile {
fn extend<T: IntoIterator<Item = (ProfileFieldName, JsonValue)>>(&mut self, iter: T) {
self.extend(iter.into_iter().map(|(field, value)| (field.as_str().to_owned(), value)));
}
}
impl Extend<ProfileFieldValue> for UserProfile {
fn extend<T: IntoIterator<Item = ProfileFieldValue>>(&mut self, iter: T) {
self.extend(iter.into_iter().map(|value| (value.field_name(), value.value().into_owned())));
}
}
impl IntoIterator for UserProfile {
type Item = (String, JsonValue);
type IntoIter = btree_map::IntoIter<String, JsonValue>;
fn into_iter(self) -> Self::IntoIter {
self.0.into_iter()
}
}
#[cfg(test)]
#[cfg(all(feature = "unstable-msc4262", feature = "unstable-msc4426"))]
mod tests {
use std::collections::BTreeMap;
use serde_json::json;
use crate::{
owned_mxc_uri,
profile::{
AvatarUrl, Call, CallProfileField, DisplayName, ProfileFieldName, ProfileFieldValue,
Status, StatusProfileField, UserProfile, UserProfileChanges,
},
};
#[test]
fn apply_profile_update() {
let mut profile = UserProfile::from_iter([
ProfileFieldValue::DisplayName("Alice".to_owned()),
ProfileFieldValue::AvatarUrl(owned_mxc_uri!("mxc://localhost/abcdef")),
ProfileFieldValue::Status(StatusProfileField {
text: "Working".to_owned(),
emoji: "🧑💻".to_owned(),
}),
]);
let mut profile_update = UserProfileChanges::new();
profile_update.removed = vec![ProfileFieldName::AvatarUrl];
profile_update.updated = BTreeMap::from([
(ProfileFieldName::Status, json!({ "text": "Holiday", "emoji": "🏖️"})),
(ProfileFieldName::Call, json!({})),
]);
profile.apply(profile_update);
assert_eq!(
profile.get_static::<DisplayName>().unwrap().unwrap(),
"Alice".to_owned(),
"The display name should be preserved."
);
assert!(
profile.get_static::<AvatarUrl>().unwrap().is_none(),
"The avatar should be removed."
);
assert_eq!(
profile.get_static::<Status>().unwrap().unwrap(),
StatusProfileField { text: "Holiday".to_owned(), emoji: "🏖️".to_owned() },
"The status should be updated."
);
assert_eq!(
profile.get_static::<Call>().unwrap().unwrap(),
CallProfileField { call_joined_ts: None },
"The call indicator should be set."
);
}
}