ruma_client_api/
profile.rs

1//! Endpoints for user profiles.
2
3use std::borrow::Cow;
4
5#[cfg(feature = "client")]
6use ruma_common::api::{
7    MatrixVersion,
8    path_builder::{StablePathSelector, VersionHistory},
9};
10use ruma_common::{OwnedMxcUri, serde::StringEnum};
11use serde::Serialize;
12use serde_json::{Value as JsonValue, from_value as from_json_value, to_value as to_json_value};
13
14pub mod delete_profile_field;
15pub mod get_avatar_url;
16pub mod get_display_name;
17pub mod get_profile;
18pub mod get_profile_field;
19mod profile_field_serde;
20pub mod set_avatar_url;
21pub mod set_display_name;
22pub mod set_profile_field;
23mod static_profile_field;
24
25pub use self::static_profile_field::*;
26
27/// The possible fields of a user's [profile].
28///
29/// [profile]: https://spec.matrix.org/latest/client-server-api/#profiles
30#[doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/src/doc/string_enum.md"))]
31#[derive(Clone, StringEnum)]
32#[ruma_enum(rename_all = "snake_case")]
33#[non_exhaustive]
34pub enum ProfileFieldName {
35    /// The user's avatar URL.
36    AvatarUrl,
37
38    /// The user's display name.
39    #[ruma_enum(rename = "displayname")]
40    DisplayName,
41
42    /// The user's time zone.
43    #[ruma_enum(rename = "m.tz")]
44    TimeZone,
45
46    #[doc(hidden)]
47    _Custom(crate::PrivOwnedStr),
48}
49
50impl ProfileFieldName {
51    /// Whether this field name existed already before custom fields were officially supported in
52    /// profiles.
53    #[cfg(feature = "client")]
54    fn existed_before_extended_profiles(&self) -> bool {
55        matches!(self, Self::AvatarUrl | Self::DisplayName)
56    }
57}
58
59/// The possible values of a field of a user's [profile].
60///
61/// [profile]: https://spec.matrix.org/latest/client-server-api/#profiles
62#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
63#[serde(rename_all = "snake_case")]
64#[non_exhaustive]
65pub enum ProfileFieldValue {
66    /// The user's avatar URL.
67    AvatarUrl(OwnedMxcUri),
68
69    /// The user's display name.
70    #[serde(rename = "displayname")]
71    DisplayName(String),
72
73    /// The user's time zone.
74    #[serde(rename = "m.tz")]
75    TimeZone(String),
76
77    #[doc(hidden)]
78    #[serde(untagged)]
79    _Custom(CustomProfileFieldValue),
80}
81
82impl ProfileFieldValue {
83    /// Construct a new `ProfileFieldValue` with the given field and value.
84    ///
85    /// Prefer to use the public variants of `ProfileFieldValue` where possible; this constructor is
86    /// meant to be used for unsupported fields only and does not allow setting arbitrary data for
87    /// supported ones.
88    ///
89    /// # Errors
90    ///
91    /// Returns an error if the `field` is known and serialization of `value` to the corresponding
92    /// `ProfileFieldValue` variant fails.
93    pub fn new(field: &str, value: JsonValue) -> serde_json::Result<Self> {
94        Ok(match field {
95            "avatar_url" => Self::AvatarUrl(from_json_value(value)?),
96            "displayname" => Self::DisplayName(from_json_value(value)?),
97            "m.tz" => Self::TimeZone(from_json_value(value)?),
98            _ => Self::_Custom(CustomProfileFieldValue { field: field.to_owned(), value }),
99        })
100    }
101
102    /// The name of the field for this value.
103    pub fn field_name(&self) -> ProfileFieldName {
104        match self {
105            Self::AvatarUrl(_) => ProfileFieldName::AvatarUrl,
106            Self::DisplayName(_) => ProfileFieldName::DisplayName,
107            Self::TimeZone(_) => ProfileFieldName::TimeZone,
108            Self::_Custom(CustomProfileFieldValue { field, .. }) => field.as_str().into(),
109        }
110    }
111
112    /// Returns the value of the field.
113    ///
114    /// Prefer to use the public variants of `ProfileFieldValue` where possible; this method is
115    /// meant to be used for custom fields only.
116    pub fn value(&self) -> Cow<'_, JsonValue> {
117        match self {
118            Self::AvatarUrl(value) => {
119                Cow::Owned(to_json_value(value).expect("value should serialize successfully"))
120            }
121            Self::DisplayName(value) => {
122                Cow::Owned(to_json_value(value).expect("value should serialize successfully"))
123            }
124            Self::TimeZone(value) => {
125                Cow::Owned(to_json_value(value).expect("value should serialize successfully"))
126            }
127            Self::_Custom(c) => Cow::Borrowed(&c.value),
128        }
129    }
130}
131
132/// A custom value for a user's profile field.
133#[derive(Debug, Clone, PartialEq, Eq)]
134#[doc(hidden)]
135pub struct CustomProfileFieldValue {
136    /// The name of the field.
137    field: String,
138
139    /// The value of the field
140    value: JsonValue,
141}
142
143/// Endpoint version history valid only for profile fields that didn't exist before Matrix 1.16.
144#[cfg(feature = "client")]
145const EXTENDED_PROFILE_FIELD_HISTORY: VersionHistory = VersionHistory::new(
146    &[(
147        Some("uk.tcpip.msc4133"),
148        "/_matrix/client/unstable/uk.tcpip.msc4133/profile/{user_id}/{field}",
149    )],
150    &[(
151        StablePathSelector::Version(MatrixVersion::V1_16),
152        "/_matrix/client/v3/profile/{user_id}/{field}",
153    )],
154    None,
155    None,
156);
157
158#[cfg(test)]
159mod tests {
160    use ruma_common::owned_mxc_uri;
161    use serde_json::{from_value as from_json_value, json, to_value as to_json_value};
162
163    use super::ProfileFieldValue;
164
165    #[test]
166    fn serialize_profile_field_value() {
167        // Avatar URL.
168        let value = ProfileFieldValue::AvatarUrl(owned_mxc_uri!("mxc://localhost/abcdef"));
169        assert_eq!(
170            to_json_value(value).unwrap(),
171            json!({ "avatar_url": "mxc://localhost/abcdef" })
172        );
173
174        // Display name.
175        let value = ProfileFieldValue::DisplayName("Alice".to_owned());
176        assert_eq!(to_json_value(value).unwrap(), json!({ "displayname": "Alice" }));
177
178        // Custom field.
179        let value = ProfileFieldValue::new("custom_field", "value".into()).unwrap();
180        assert_eq!(to_json_value(value).unwrap(), json!({ "custom_field": "value" }));
181    }
182
183    #[test]
184    fn deserialize_any_profile_field_value() {
185        // Avatar URL.
186        let json = json!({ "avatar_url": "mxc://localhost/abcdef" });
187        assert_eq!(
188            from_json_value::<ProfileFieldValue>(json).unwrap(),
189            ProfileFieldValue::AvatarUrl(owned_mxc_uri!("mxc://localhost/abcdef"))
190        );
191
192        // Display name.
193        let json = json!({ "displayname": "Alice" });
194        assert_eq!(
195            from_json_value::<ProfileFieldValue>(json).unwrap(),
196            ProfileFieldValue::DisplayName("Alice".to_owned())
197        );
198
199        // Custom field.
200        let json = json!({ "custom_field": "value" });
201        let value = from_json_value::<ProfileFieldValue>(json).unwrap();
202        assert_eq!(value.field_name().as_str(), "custom_field");
203        assert_eq!(value.value().as_str(), Some("value"));
204
205        // Error if the object is empty.
206        let json = json!({});
207        from_json_value::<ProfileFieldValue>(json).unwrap_err();
208    }
209}