ruma_common/profile/
user_profile.rs1use std::collections::{BTreeMap, btree_map};
4
5use serde::{Deserialize, Serialize};
6use serde_json::Value as JsonValue;
7
8#[cfg(feature = "unstable-msc4262")]
9use super::UserProfileUpdate;
10use super::{ProfileFieldName, ProfileFieldValue, static_profile_field::StaticProfileField};
11
12#[derive(Clone, Debug, Default, Serialize, Deserialize)]
14#[serde(transparent)]
15pub struct UserProfile(BTreeMap<String, JsonValue>);
16
17impl UserProfile {
18 pub fn new() -> Self {
20 Self::default()
21 }
22
23 pub fn get(&self, field: &str) -> Option<&JsonValue> {
25 self.0.get(field)
26 }
27
28 pub fn get_static<F: StaticProfileField>(&self) -> Result<Option<F::Value>, serde_json::Error> {
34 self.0.get(F::NAME).map(|value| serde_json::from_value(value.clone())).transpose()
35 }
36
37 pub fn iter(&self) -> btree_map::Iter<'_, String, JsonValue> {
39 self.0.iter()
40 }
41
42 pub fn set(&mut self, field: String, value: JsonValue) {
44 self.0.insert(field, value);
45 }
46
47 #[cfg(feature = "unstable-msc4262")]
52 pub fn merge(&mut self, profile_update: UserProfileUpdate) {
53 for (field, value) in profile_update {
54 if value.is_null() {
55 self.0.remove(&field);
56 } else {
57 self.0.insert(field, value);
58 }
59 }
60 }
61}
62
63impl FromIterator<(String, JsonValue)> for UserProfile {
64 fn from_iter<T: IntoIterator<Item = (String, JsonValue)>>(iter: T) -> Self {
65 Self(iter.into_iter().collect())
66 }
67}
68
69impl FromIterator<(ProfileFieldName, JsonValue)> for UserProfile {
70 fn from_iter<T: IntoIterator<Item = (ProfileFieldName, JsonValue)>>(iter: T) -> Self {
71 iter.into_iter().map(|(field, value)| (field.as_str().to_owned(), value)).collect()
72 }
73}
74
75impl FromIterator<ProfileFieldValue> for UserProfile {
76 fn from_iter<T: IntoIterator<Item = ProfileFieldValue>>(iter: T) -> Self {
77 iter.into_iter().map(|value| (value.field_name(), value.value().into_owned())).collect()
78 }
79}
80
81impl Extend<(String, JsonValue)> for UserProfile {
82 fn extend<T: IntoIterator<Item = (String, JsonValue)>>(&mut self, iter: T) {
83 self.0.extend(iter);
84 }
85}
86
87impl Extend<(ProfileFieldName, JsonValue)> for UserProfile {
88 fn extend<T: IntoIterator<Item = (ProfileFieldName, JsonValue)>>(&mut self, iter: T) {
89 self.extend(iter.into_iter().map(|(field, value)| (field.as_str().to_owned(), value)));
90 }
91}
92
93impl Extend<ProfileFieldValue> for UserProfile {
94 fn extend<T: IntoIterator<Item = ProfileFieldValue>>(&mut self, iter: T) {
95 self.extend(iter.into_iter().map(|value| (value.field_name(), value.value().into_owned())));
96 }
97}
98
99impl IntoIterator for UserProfile {
100 type Item = (String, JsonValue);
101 type IntoIter = btree_map::IntoIter<String, JsonValue>;
102
103 fn into_iter(self) -> Self::IntoIter {
104 self.0.into_iter()
105 }
106}
107
108#[cfg(test)]
109#[cfg(all(feature = "unstable-msc4262", feature = "unstable-msc4426"))]
110mod tests {
111 use serde_json::{Value as JsonValue, json};
112
113 use crate::{
114 owned_mxc_uri,
115 profile::{
116 AvatarUrl, Call, CallProfileField, DisplayName, ProfileFieldValue, Status,
117 StatusProfileField, UserProfile, UserProfileUpdate,
118 },
119 };
120
121 #[test]
122 fn merge_profile() {
123 let mut profile = UserProfile::from_iter([
124 ProfileFieldValue::DisplayName("Alice".to_owned()),
125 ProfileFieldValue::AvatarUrl(owned_mxc_uri!("mxc://localhost/abcdef")),
126 ProfileFieldValue::Status(StatusProfileField {
127 text: "Working".to_owned(),
128 emoji: "🧑💻".to_owned(),
129 }),
130 ]);
131
132 let profile_update = UserProfileUpdate::from_iter([
133 ("avatar_url".to_owned(), JsonValue::Null),
134 ("org.matrix.msc4426.status".to_owned(), json!({ "text": "Holiday", "emoji": "🏖️"})),
135 ("org.matrix.msc4426.call".to_owned(), json!({})),
136 ]);
137
138 profile.merge(profile_update);
139
140 assert_eq!(
141 profile.get_static::<DisplayName>().unwrap().unwrap(),
142 "Alice".to_owned(),
143 "The display name should be preserved."
144 );
145 assert!(
146 profile.get_static::<AvatarUrl>().unwrap().is_none(),
147 "The avatar should be removed."
148 );
149 assert_eq!(
150 profile.get_static::<Status>().unwrap().unwrap(),
151 StatusProfileField { text: "Holiday".to_owned(), emoji: "🏖️".to_owned() },
152 "The status should be updated."
153 );
154 assert_eq!(
155 profile.get_static::<Call>().unwrap().unwrap(),
156 CallProfileField { call_joined_ts: None },
157 "The call indicator should be set."
158 );
159 }
160}