Skip to main content

ruma_common/
profile.rs

1//! Common types for user profile endpoints.
2
3use std::borrow::Cow;
4
5use ruma_macros::StringEnum;
6use serde::Serialize;
7use serde_json::{Value as JsonValue, from_value as from_json_value, to_value as to_json_value};
8
9use crate::{OwnedMxcUri, PrivOwnedStr};
10
11mod profile_field_value_serde;
12
13#[doc(hidden)]
14pub use self::profile_field_value_serde::ProfileFieldValueVisitor;
15
16/// The possible fields of a user's [profile].
17///
18/// [profile]: https://spec.matrix.org/v1.18/client-server-api/#profiles
19#[doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/src/doc/string_enum.md"))]
20#[derive(Clone, StringEnum)]
21#[ruma_enum(rename_all = "snake_case")]
22#[non_exhaustive]
23pub enum ProfileFieldName {
24    /// The user's avatar URL.
25    AvatarUrl,
26
27    /// The user's display name.
28    #[ruma_enum(rename = "displayname")]
29    DisplayName,
30
31    /// The user's time zone.
32    #[ruma_enum(rename = "m.tz")]
33    TimeZone,
34
35    #[doc(hidden)]
36    _Custom(PrivOwnedStr),
37}
38
39/// The possible values of a field of a user's [profile].
40///
41/// [profile]: https://spec.matrix.org/v1.18/client-server-api/#profiles
42#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
43#[serde(rename_all = "snake_case")]
44#[non_exhaustive]
45pub enum ProfileFieldValue {
46    /// The user's avatar URL.
47    AvatarUrl(OwnedMxcUri),
48
49    /// The user's display name.
50    #[serde(rename = "displayname")]
51    DisplayName(String),
52
53    /// The user's time zone.
54    #[serde(rename = "m.tz")]
55    TimeZone(String),
56
57    #[doc(hidden)]
58    #[serde(untagged)]
59    _Custom(CustomProfileFieldValue),
60}
61
62impl ProfileFieldValue {
63    /// Construct a new `ProfileFieldValue` with the given field and value.
64    ///
65    /// Prefer to use the public variants of `ProfileFieldValue` where possible; this constructor is
66    /// meant to be used for unsupported fields only and does not allow setting arbitrary data for
67    /// supported ones.
68    ///
69    /// # Errors
70    ///
71    /// Returns an error if the `field` is known and serialization of `value` to the corresponding
72    /// `ProfileFieldValue` variant fails.
73    pub fn new(field: &str, value: JsonValue) -> serde_json::Result<Self> {
74        Ok(match field {
75            "avatar_url" => Self::AvatarUrl(from_json_value(value)?),
76            "displayname" => Self::DisplayName(from_json_value(value)?),
77            "m.tz" => Self::TimeZone(from_json_value(value)?),
78            _ => Self::_Custom(CustomProfileFieldValue { field: field.to_owned(), value }),
79        })
80    }
81
82    /// The name of the field for this value.
83    pub fn field_name(&self) -> ProfileFieldName {
84        match self {
85            Self::AvatarUrl(_) => ProfileFieldName::AvatarUrl,
86            Self::DisplayName(_) => ProfileFieldName::DisplayName,
87            Self::TimeZone(_) => ProfileFieldName::TimeZone,
88            Self::_Custom(CustomProfileFieldValue { field, .. }) => field.as_str().into(),
89        }
90    }
91
92    /// Returns the value of the field.
93    ///
94    /// Prefer to use the public variants of `ProfileFieldValue` where possible; this method is
95    /// meant to be used for custom fields only.
96    pub fn value(&self) -> Cow<'_, JsonValue> {
97        match self {
98            Self::AvatarUrl(value) => {
99                Cow::Owned(to_json_value(value).expect("value should serialize successfully"))
100            }
101            Self::DisplayName(value) => {
102                Cow::Owned(to_json_value(value).expect("value should serialize successfully"))
103            }
104            Self::TimeZone(value) => {
105                Cow::Owned(to_json_value(value).expect("value should serialize successfully"))
106            }
107            Self::_Custom(c) => Cow::Borrowed(&c.value),
108        }
109    }
110}
111
112/// A custom value for a user's profile field.
113#[derive(Debug, Clone, PartialEq, Eq)]
114#[doc(hidden)]
115pub struct CustomProfileFieldValue {
116    /// The name of the field.
117    field: String,
118
119    /// The value of the field
120    value: JsonValue,
121}
122
123#[cfg(test)]
124mod tests {
125    use ruma_common::{canonical_json::assert_to_canonical_json_eq, owned_mxc_uri};
126    use serde_json::{from_value as from_json_value, json};
127
128    use super::ProfileFieldValue;
129
130    #[test]
131    fn serialize_profile_field_value() {
132        // Avatar URL.
133        let value = ProfileFieldValue::AvatarUrl(owned_mxc_uri!("mxc://localhost/abcdef"));
134        assert_to_canonical_json_eq!(value, json!({ "avatar_url": "mxc://localhost/abcdef" }));
135
136        // Display name.
137        let value = ProfileFieldValue::DisplayName("Alice".to_owned());
138        assert_to_canonical_json_eq!(value, json!({ "displayname": "Alice" }));
139
140        // Custom field.
141        let value = ProfileFieldValue::new("custom_field", "value".into()).unwrap();
142        assert_to_canonical_json_eq!(value, json!({ "custom_field": "value" }));
143    }
144
145    #[test]
146    fn deserialize_profile_field_value() {
147        // Avatar URL.
148        let json = json!({ "avatar_url": "mxc://localhost/abcdef" });
149        assert_eq!(
150            from_json_value::<ProfileFieldValue>(json).unwrap(),
151            ProfileFieldValue::AvatarUrl(owned_mxc_uri!("mxc://localhost/abcdef"))
152        );
153
154        // Display name.
155        let json = json!({ "displayname": "Alice" });
156        assert_eq!(
157            from_json_value::<ProfileFieldValue>(json).unwrap(),
158            ProfileFieldValue::DisplayName("Alice".to_owned())
159        );
160
161        // Custom field.
162        let json = json!({ "custom_field": "value" });
163        let value = from_json_value::<ProfileFieldValue>(json).unwrap();
164        assert_eq!(value.field_name().as_str(), "custom_field");
165        assert_eq!(value.value().as_str(), Some("value"));
166
167        // Error if the object is empty.
168        let json = json!({});
169        from_json_value::<ProfileFieldValue>(json).unwrap_err();
170    }
171}