ruma_client_api/
profile.rs1use std::borrow::Cow;
4
5use ruma_common::{
6 api::{MatrixVersion, StablePathSelector, VersionHistory},
7 serde::StringEnum,
8 OwnedMxcUri,
9};
10use serde::Serialize;
11use serde_json::{from_value as from_json_value, to_value as to_json_value, Value as JsonValue};
12
13pub mod delete_profile_field;
14pub mod get_avatar_url;
15pub mod get_display_name;
16pub mod get_profile;
17pub mod get_profile_field;
18mod profile_field_serde;
19pub mod set_avatar_url;
20pub mod set_display_name;
21pub mod set_profile_field;
22mod static_profile_field;
23
24pub use self::static_profile_field::*;
25
26#[doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/src/doc/string_enum.md"))]
30#[derive(Clone, StringEnum)]
31#[ruma_enum(rename_all = "snake_case")]
32#[non_exhaustive]
33pub enum ProfileFieldName {
34 AvatarUrl,
36
37 #[ruma_enum(rename = "displayname")]
39 DisplayName,
40
41 #[ruma_enum(rename = "m.tz")]
43 TimeZone,
44
45 #[doc(hidden)]
46 _Custom(crate::PrivOwnedStr),
47}
48
49impl ProfileFieldName {
50 fn existed_before_extended_profiles(&self) -> bool {
53 matches!(self, Self::AvatarUrl | Self::DisplayName)
54 }
55}
56
57#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
61#[serde(rename_all = "snake_case")]
62#[non_exhaustive]
63pub enum ProfileFieldValue {
64 AvatarUrl(OwnedMxcUri),
66
67 #[serde(rename = "displayname")]
69 DisplayName(String),
70
71 #[serde(rename = "m.tz")]
73 TimeZone(String),
74
75 #[doc(hidden)]
76 #[serde(untagged)]
77 _Custom(CustomProfileFieldValue),
78}
79
80impl ProfileFieldValue {
81 pub fn new(field: &str, value: JsonValue) -> serde_json::Result<Self> {
92 Ok(match field {
93 "avatar_url" => Self::AvatarUrl(from_json_value(value)?),
94 "displayname" => Self::DisplayName(from_json_value(value)?),
95 "m.tz" => Self::TimeZone(from_json_value(value)?),
96 _ => Self::_Custom(CustomProfileFieldValue { field: field.to_owned(), value }),
97 })
98 }
99
100 pub fn field_name(&self) -> ProfileFieldName {
102 match self {
103 Self::AvatarUrl(_) => ProfileFieldName::AvatarUrl,
104 Self::DisplayName(_) => ProfileFieldName::DisplayName,
105 Self::TimeZone(_) => ProfileFieldName::TimeZone,
106 Self::_Custom(CustomProfileFieldValue { field, .. }) => field.as_str().into(),
107 }
108 }
109
110 pub fn value(&self) -> Cow<'_, JsonValue> {
115 match self {
116 Self::AvatarUrl(value) => {
117 Cow::Owned(to_json_value(value).expect("value should serialize successfully"))
118 }
119 Self::DisplayName(value) => {
120 Cow::Owned(to_json_value(value).expect("value should serialize successfully"))
121 }
122 Self::TimeZone(value) => {
123 Cow::Owned(to_json_value(value).expect("value should serialize successfully"))
124 }
125 Self::_Custom(c) => Cow::Borrowed(&c.value),
126 }
127 }
128}
129
130#[derive(Debug, Clone, PartialEq, Eq)]
132#[doc(hidden)]
133pub struct CustomProfileFieldValue {
134 field: String,
136
137 value: JsonValue,
139}
140
141const EXTENDED_PROFILE_FIELD_HISTORY: VersionHistory = VersionHistory::new(
143 &[(
144 Some("uk.tcpip.msc4133"),
145 "/_matrix/client/unstable/uk.tcpip.msc4133/profile/{user_id}/{field}",
146 )],
147 &[(
148 StablePathSelector::Version(MatrixVersion::V1_16),
149 "/_matrix/client/v3/profile/{user_id}/{field}",
150 )],
151 None,
152 None,
153);
154
155#[cfg(test)]
156mod tests {
157 use ruma_common::owned_mxc_uri;
158 use serde_json::{from_value as from_json_value, json, to_value as to_json_value};
159
160 use super::ProfileFieldValue;
161
162 #[test]
163 fn serialize_profile_field_value() {
164 let value = ProfileFieldValue::AvatarUrl(owned_mxc_uri!("mxc://localhost/abcdef"));
166 assert_eq!(
167 to_json_value(value).unwrap(),
168 json!({ "avatar_url": "mxc://localhost/abcdef" })
169 );
170
171 let value = ProfileFieldValue::DisplayName("Alice".to_owned());
173 assert_eq!(to_json_value(value).unwrap(), json!({ "displayname": "Alice" }));
174
175 let value = ProfileFieldValue::new("custom_field", "value".into()).unwrap();
177 assert_eq!(to_json_value(value).unwrap(), json!({ "custom_field": "value" }));
178 }
179
180 #[test]
181 fn deserialize_any_profile_field_value() {
182 let json = json!({ "avatar_url": "mxc://localhost/abcdef" });
184 assert_eq!(
185 from_json_value::<ProfileFieldValue>(json).unwrap(),
186 ProfileFieldValue::AvatarUrl(owned_mxc_uri!("mxc://localhost/abcdef"))
187 );
188
189 let json = json!({ "displayname": "Alice" });
191 assert_eq!(
192 from_json_value::<ProfileFieldValue>(json).unwrap(),
193 ProfileFieldValue::DisplayName("Alice".to_owned())
194 );
195
196 let json = json!({ "custom_field": "value" });
198 let value = from_json_value::<ProfileFieldValue>(json).unwrap();
199 assert_eq!(value.field_name().as_str(), "custom_field");
200 assert_eq!(value.value().as_str(), Some("value"));
201
202 let json = json!({});
204 from_json_value::<ProfileFieldValue>(json).unwrap_err();
205 }
206}