ruma_client_api/
profile.rs

1//! Endpoints for user profiles.
2
3use 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/// The possible fields of a user's [profile].
27///
28/// [profile]: https://spec.matrix.org/latest/client-server-api/#profiles
29#[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    /// The user's avatar URL.
35    AvatarUrl,
36
37    /// The user's display name.
38    #[ruma_enum(rename = "displayname")]
39    DisplayName,
40
41    /// The user's time zone.
42    #[ruma_enum(rename = "m.tz")]
43    TimeZone,
44
45    #[doc(hidden)]
46    _Custom(crate::PrivOwnedStr),
47}
48
49impl ProfileFieldName {
50    /// Whether this field name existed already before custom fields were officially supported in
51    /// profiles.
52    fn existed_before_extended_profiles(&self) -> bool {
53        matches!(self, Self::AvatarUrl | Self::DisplayName)
54    }
55}
56
57/// The possible values of a field of a user's [profile].
58///
59/// [profile]: https://spec.matrix.org/latest/client-server-api/#profiles
60#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
61#[serde(rename_all = "snake_case")]
62#[non_exhaustive]
63pub enum ProfileFieldValue {
64    /// The user's avatar URL.
65    AvatarUrl(OwnedMxcUri),
66
67    /// The user's display name.
68    #[serde(rename = "displayname")]
69    DisplayName(String),
70
71    /// The user's time zone.
72    #[serde(rename = "m.tz")]
73    TimeZone(String),
74
75    #[doc(hidden)]
76    #[serde(untagged)]
77    _Custom(CustomProfileFieldValue),
78}
79
80impl ProfileFieldValue {
81    /// Construct a new `ProfileFieldValue` with the given field and value.
82    ///
83    /// Prefer to use the public variants of `ProfileFieldValue` where possible; this constructor is
84    /// meant to be used for unsupported fields only and does not allow setting arbitrary data for
85    /// supported ones.
86    ///
87    /// # Errors
88    ///
89    /// Returns an error if the `field` is known and serialization of `value` to the corresponding
90    /// `ProfileFieldValue` variant fails.
91    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    /// The name of the field for this value.
101    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    /// Returns the value of the field.
111    ///
112    /// Prefer to use the public variants of `ProfileFieldValue` where possible; this method is
113    /// meant to be used for custom fields only.
114    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/// A custom value for a user's profile field.
131#[derive(Debug, Clone, PartialEq, Eq)]
132#[doc(hidden)]
133pub struct CustomProfileFieldValue {
134    /// The name of the field.
135    field: String,
136
137    /// The value of the field
138    value: JsonValue,
139}
140
141/// Endpoint version history valid only for profile fields that didn't exist before Matrix 1.16.
142const 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        // Avatar URL.
165        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        // Display name.
172        let value = ProfileFieldValue::DisplayName("Alice".to_owned());
173        assert_eq!(to_json_value(value).unwrap(), json!({ "displayname": "Alice" }));
174
175        // Custom field.
176        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        // Avatar URL.
183        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        // Display name.
190        let json = json!({ "displayname": "Alice" });
191        assert_eq!(
192            from_json_value::<ProfileFieldValue>(json).unwrap(),
193            ProfileFieldValue::DisplayName("Alice".to_owned())
194        );
195
196        // Custom field.
197        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        // Error if the object is empty.
203        let json = json!({});
204        from_json_value::<ProfileFieldValue>(json).unwrap_err();
205    }
206}