ruma_client_api/profile/
set_profile_field.rs

1//! `PUT /_matrix/client/*/profile/{userId}/{key_name}`
2//!
3//! Set a field on the profile of the user.
4
5pub mod v3 {
6    //! `/v3/` ([spec])
7    //!
8    //! [spec]: https://github.com/matrix-org/matrix-spec-proposals/pull/4133
9
10    use ruma_common::{
11        api::{response, Metadata},
12        metadata, OwnedUserId,
13    };
14
15    use crate::profile::{profile_field_serde::ProfileFieldValueVisitor, ProfileFieldValue};
16
17    const METADATA: Metadata = metadata! {
18        method: PUT,
19        rate_limited: true,
20        authentication: AccessToken,
21        history: {
22            unstable("uk.tcpip.msc4133") => "/_matrix/client/unstable/uk.tcpip.msc4133/profile/{user_id}/{field}",
23            // 1.15 => "/_matrix/client/v3/profile/{user_id}/{field}",
24        }
25    };
26
27    /// Request type for the `set_profile_field` endpoint.
28    #[derive(Debug, Clone)]
29    #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
30    pub struct Request {
31        /// The user whose profile will be updated.
32        pub user_id: OwnedUserId,
33
34        /// The value of the profile field to set.
35        pub value: ProfileFieldValue,
36    }
37
38    impl Request {
39        /// Creates a new `Request` with the given user ID, field and value.
40        pub fn new(user_id: OwnedUserId, value: ProfileFieldValue) -> Self {
41            Self { user_id, value }
42        }
43    }
44
45    #[cfg(feature = "client")]
46    impl ruma_common::api::OutgoingRequest for Request {
47        type EndpointError = crate::Error;
48        type IncomingResponse = Response;
49
50        const METADATA: Metadata = METADATA;
51
52        fn try_into_http_request<T: Default + bytes::BufMut>(
53            self,
54            base_url: &str,
55            _access_token: ruma_common::api::SendAccessToken<'_>,
56            considering: &'_ ruma_common::api::SupportedVersions,
57        ) -> Result<http::Request<T>, ruma_common::api::error::IntoHttpError> {
58            let url = METADATA.make_endpoint_url(
59                considering,
60                base_url,
61                &[&self.user_id, &self.value.field_name()],
62                "",
63            )?;
64
65            let http_request = http::Request::builder()
66                .method(METADATA.method)
67                .uri(url)
68                .body(ruma_common::serde::json_to_buf(&self.value)?)
69                // this cannot fail because we don't give user-supplied data to any of the
70                // builder methods
71                .unwrap();
72
73            Ok(http_request)
74        }
75    }
76
77    #[cfg(feature = "server")]
78    impl ruma_common::api::IncomingRequest for Request {
79        type EndpointError = crate::Error;
80        type OutgoingResponse = Response;
81
82        const METADATA: Metadata = METADATA;
83
84        fn try_from_http_request<B, S>(
85            request: http::Request<B>,
86            path_args: &[S],
87        ) -> Result<Self, ruma_common::api::error::FromHttpRequestError>
88        where
89            B: AsRef<[u8]>,
90            S: AsRef<str>,
91        {
92            use serde::de::{Deserializer, Error as _};
93
94            use crate::profile::ProfileFieldName;
95
96            let (user_id, field): (OwnedUserId, ProfileFieldName) =
97                serde::Deserialize::deserialize(serde::de::value::SeqDeserializer::<
98                    _,
99                    serde::de::value::Error,
100                >::new(
101                    path_args.iter().map(::std::convert::AsRef::as_ref),
102                ))?;
103
104            let value = serde_json::Deserializer::from_slice(request.body().as_ref())
105                .deserialize_map(ProfileFieldValueVisitor(Some(field.clone())))?
106                .ok_or_else(|| serde_json::Error::custom(format!("missing field `{field}`")))?;
107
108            Ok(Request { user_id, value })
109        }
110    }
111
112    /// Response type for the `set_profile_field` endpoint.
113    #[response(error = crate::Error)]
114    #[derive(Default)]
115    pub struct Response {}
116
117    impl Response {
118        /// Creates an empty `Response`.
119        pub fn new() -> Self {
120            Self {}
121        }
122    }
123}
124
125#[cfg(test)]
126mod tests {
127    use assert_matches2::assert_matches;
128    use ruma_common::{owned_mxc_uri, owned_user_id};
129    use serde_json::{
130        from_slice as from_json_slice, json, to_vec as to_json_vec, Value as JsonValue,
131    };
132
133    use super::v3::Request;
134    use crate::profile::ProfileFieldValue;
135
136    #[test]
137    #[cfg(feature = "client")]
138    fn serialize_request() {
139        use ruma_common::api::{OutgoingRequest, SendAccessToken, SupportedVersions};
140
141        let request = Request::new(
142            owned_user_id!("@alice:localhost"),
143            ProfileFieldValue::AvatarUrl(owned_mxc_uri!("mxc://localhost/abcdef")),
144        );
145
146        let http_request = request
147            .try_into_http_request::<Vec<u8>>(
148                "http://localhost/",
149                SendAccessToken::Always("access_token"),
150                &SupportedVersions::from_parts(&["v11".to_owned()], &Default::default()),
151            )
152            .unwrap();
153
154        assert_eq!(
155            http_request.uri().path(),
156            "/_matrix/client/unstable/uk.tcpip.msc4133/profile/@alice:localhost/avatar_url"
157        );
158        assert_eq!(
159            from_json_slice::<JsonValue>(http_request.body().as_ref()).unwrap(),
160            json!({
161                "avatar_url": "mxc://localhost/abcdef",
162            })
163        );
164    }
165
166    #[test]
167    #[cfg(feature = "server")]
168    fn deserialize_request_valid_field() {
169        use ruma_common::api::IncomingRequest;
170
171        let body = to_json_vec(&json!({
172            "displayname": "Alice",
173        }))
174        .unwrap();
175
176        let request = Request::try_from_http_request(
177            http::Request::put("http://localhost/_matrix/client/unstable/uk.tcpip.msc4133/profile/@alice:localhost/displayname").body(body).unwrap(),
178            &["@alice:localhost", "displayname"],
179        ).unwrap();
180
181        assert_eq!(request.user_id, "@alice:localhost");
182        assert_matches!(request.value, ProfileFieldValue::DisplayName(display_name));
183        assert_eq!(display_name, "Alice");
184    }
185
186    #[test]
187    #[cfg(feature = "server")]
188    fn deserialize_request_invalid_field() {
189        use ruma_common::api::IncomingRequest;
190
191        let body = to_json_vec(&json!({
192            "custom_field": "value",
193        }))
194        .unwrap();
195
196        Request::try_from_http_request(
197            http::Request::put("http://localhost/_matrix/client/unstable/uk.tcpip.msc4133/profile/@alice:localhost/displayname").body(body).unwrap(),
198            &["@alice:localhost", "displayname"],
199        ).unwrap_err();
200    }
201}