ruma_client_api/
profile.rs

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