Skip to main content

ruma_client_api/discovery/
discover_homeserver.rs

1//! `GET /.well-known/matrix/client` ([spec])
2//!
3//! [spec]: https://spec.matrix.org/v1.18/client-server-api/#getwell-knownmatrixclient
4//!
5//! Get discovery information about the domain.
6
7#[cfg(feature = "unstable-msc4143")]
8use std::borrow::Cow;
9
10#[cfg(feature = "unstable-msc4143")]
11use ruma_common::serde::{JsonObject, from_raw_json_value};
12use ruma_common::{
13    api::{auth_scheme::NoAccessToken, request, response},
14    metadata,
15};
16use serde::{Deserialize, Serialize};
17#[cfg(feature = "unstable-msc4143")]
18use serde::{Deserializer, de::DeserializeOwned};
19#[cfg(feature = "unstable-msc4143")]
20use serde_json::Value as JsonValue;
21#[cfg(feature = "unstable-msc4143")]
22use serde_json::value::RawValue as RawJsonValue;
23
24metadata! {
25    method: GET,
26    rate_limited: false,
27    authentication: NoAccessToken,
28    path: "/.well-known/matrix/client",
29}
30
31/// Request type for the `client_well_known` endpoint.
32#[request]
33#[derive(Default)]
34pub struct Request {}
35
36/// Response type for the `client_well_known` endpoint.
37#[response]
38pub struct Response {
39    /// Information about the homeserver to connect to.
40    #[serde(rename = "m.homeserver")]
41    pub homeserver: HomeserverInfo,
42
43    /// Information about the identity server to connect to.
44    #[serde(rename = "m.identity_server", skip_serializing_if = "Option::is_none")]
45    pub identity_server: Option<IdentityServerInfo>,
46
47    /// Information about the tile server to use to display location data.
48    #[cfg(feature = "unstable-msc3488")]
49    #[serde(
50        rename = "org.matrix.msc3488.tile_server",
51        alias = "m.tile_server",
52        skip_serializing_if = "Option::is_none"
53    )]
54    pub tile_server: Option<TileServerInfo>,
55
56    /// A list of the available MatrixRTC foci, ordered by priority.
57    #[cfg(feature = "unstable-msc4143")]
58    #[serde(
59        rename = "org.matrix.msc4143.rtc_foci",
60        alias = "m.rtc_foci",
61        default,
62        skip_serializing_if = "Vec::is_empty"
63    )]
64    pub rtc_foci: Vec<RtcFocusInfo>,
65}
66
67impl Request {
68    /// Creates an empty `Request`.
69    pub fn new() -> Self {
70        Self {}
71    }
72}
73
74impl Response {
75    /// Creates a new `Response` with the given `HomeserverInfo`.
76    pub fn new(homeserver: HomeserverInfo) -> Self {
77        Self {
78            homeserver,
79            identity_server: None,
80            #[cfg(feature = "unstable-msc3488")]
81            tile_server: None,
82            #[cfg(feature = "unstable-msc4143")]
83            rtc_foci: Default::default(),
84        }
85    }
86}
87
88/// Information about a discovered homeserver.
89#[derive(Clone, Debug, Deserialize, Hash, Serialize, PartialEq, Eq)]
90#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
91pub struct HomeserverInfo {
92    /// The base URL for the homeserver for client-server connections.
93    pub base_url: String,
94}
95
96impl HomeserverInfo {
97    /// Creates a new `HomeserverInfo` with the given `base_url`.
98    pub fn new(base_url: String) -> Self {
99        Self { base_url }
100    }
101}
102
103/// Information about a discovered identity server.
104#[derive(Clone, Debug, Deserialize, Hash, Serialize, PartialEq, Eq)]
105#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
106pub struct IdentityServerInfo {
107    /// The base URL for the identity server for client-server connections.
108    pub base_url: String,
109}
110
111impl IdentityServerInfo {
112    /// Creates an `IdentityServerInfo` with the given `base_url`.
113    pub fn new(base_url: String) -> Self {
114        Self { base_url }
115    }
116}
117
118/// Information about a discovered map tile server.
119#[cfg(feature = "unstable-msc3488")]
120#[derive(Clone, Debug, Deserialize, Hash, Serialize, PartialEq, Eq)]
121#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
122pub struct TileServerInfo {
123    /// The URL of a map tile server's `style.json` file.
124    ///
125    /// See the [Mapbox Style Specification](https://docs.mapbox.com/mapbox-gl-js/style-spec/) for more details.
126    pub map_style_url: String,
127}
128
129#[cfg(feature = "unstable-msc3488")]
130impl TileServerInfo {
131    /// Creates a `TileServerInfo` with the given map style URL.
132    pub fn new(map_style_url: String) -> Self {
133        Self { map_style_url }
134    }
135}
136
137/// Information about a specific MatrixRTC focus.
138#[cfg(feature = "unstable-msc4143")]
139#[derive(Clone, Debug, Serialize, PartialEq, Eq)]
140#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
141#[serde(tag = "type")]
142pub enum RtcFocusInfo {
143    /// A LiveKit RTC focus.
144    #[serde(rename = "livekit")]
145    LiveKit(LiveKitRtcFocusInfo),
146
147    /// A custom RTC focus.
148    #[doc(hidden)]
149    #[serde(untagged)]
150    _Custom(CustomRtcFocusInfo),
151}
152
153#[cfg(feature = "unstable-msc4143")]
154impl RtcFocusInfo {
155    /// A constructor to create a custom RTC focus.
156    ///
157    /// Prefer to use the public variants of `RtcFocusInfo` where possible; this constructor is
158    /// meant to be used for unsupported focus types only and does not allow setting arbitrary data
159    /// for supported ones.
160    ///
161    /// # Errors
162    ///
163    /// Returns an error if the `focus_type` is known and serialization of `data` to the
164    /// corresponding `RtcFocusInfo` variant fails.
165    pub fn new(focus_type: &str, data: JsonObject) -> serde_json::Result<Self> {
166        fn deserialize_variant<T: DeserializeOwned>(obj: JsonObject) -> serde_json::Result<T> {
167            serde_json::from_value(JsonValue::Object(obj))
168        }
169
170        Ok(match focus_type {
171            "livekit" => Self::LiveKit(deserialize_variant(data)?),
172            _ => Self::_Custom(CustomRtcFocusInfo { focus_type: focus_type.to_owned(), data }),
173        })
174    }
175
176    /// Creates a new `RtcFocusInfo::LiveKit`.
177    pub fn livekit(service_url: String) -> Self {
178        Self::LiveKit(LiveKitRtcFocusInfo { service_url })
179    }
180
181    /// Returns a reference to the focus type of this RTC focus.
182    pub fn focus_type(&self) -> &str {
183        match self {
184            Self::LiveKit(_) => "livekit",
185            Self::_Custom(custom) => &custom.focus_type,
186        }
187    }
188
189    /// Returns the associated data.
190    ///
191    /// The returned JSON object won't contain the `focus_type` field, please use
192    /// [`.focus_type()`][Self::focus_type] to access that.
193    ///
194    /// Prefer to use the public variants of `RtcFocusInfo` where possible; this method is meant to
195    /// be used for custom focus types only.
196    pub fn data(&self) -> Cow<'_, JsonObject> {
197        fn serialize<T: Serialize>(object: &T) -> JsonObject {
198            match serde_json::to_value(object).expect("rtc focus type serialization to succeed") {
199                JsonValue::Object(object) => object,
200                _ => panic!("all rtc focus types must serialize to objects"),
201            }
202        }
203
204        match self {
205            Self::LiveKit(info) => Cow::Owned(serialize(info)),
206            Self::_Custom(info) => Cow::Borrowed(&info.data),
207        }
208    }
209}
210
211#[cfg(feature = "unstable-msc4143")]
212impl<'de> Deserialize<'de> for RtcFocusInfo {
213    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
214    where
215        D: Deserializer<'de>,
216    {
217        #[derive(Deserialize)]
218        struct RtcFocusInfoDeHelper {
219            #[serde(rename = "type")]
220            focus_type: String,
221        }
222
223        let json = Box::<RawJsonValue>::deserialize(deserializer)?;
224        let RtcFocusInfoDeHelper { focus_type } = from_raw_json_value(&json)?;
225
226        Ok(match focus_type.as_ref() {
227            "livekit" => Self::LiveKit(from_raw_json_value(&json)?),
228            _ => {
229                let mut data = from_raw_json_value::<JsonObject, _>(&json)?;
230                data.remove("type");
231
232                Self::_Custom(CustomRtcFocusInfo { focus_type, data })
233            }
234        })
235    }
236}
237
238/// Information about a LiveKit RTC focus.
239#[cfg(feature = "unstable-msc4143")]
240#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
241#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
242pub struct LiveKitRtcFocusInfo {
243    /// The URL for the LiveKit service.
244    #[serde(rename = "livekit_service_url")]
245    pub service_url: String,
246}
247
248/// Information about a custom RTC focus type.
249#[doc(hidden)]
250#[cfg(feature = "unstable-msc4143")]
251#[derive(Clone, Debug, Serialize, PartialEq, Eq)]
252pub struct CustomRtcFocusInfo {
253    /// The type of RTC focus.
254    #[serde(rename = "type")]
255    focus_type: String,
256
257    /// Remaining RTC focus data.
258    #[serde(flatten)]
259    data: JsonObject,
260}
261
262#[cfg(test)]
263mod tests {
264    #[cfg(feature = "unstable-msc4143")]
265    use assert_matches2::assert_let;
266    #[cfg(feature = "unstable-msc4143")]
267    use ruma_common::canonical_json::assert_to_canonical_json_eq;
268    #[cfg(feature = "unstable-msc4143")]
269    use serde_json::{from_value as from_json_value, json};
270
271    #[cfg(feature = "unstable-msc4143")]
272    use super::RtcFocusInfo;
273
274    #[test]
275    #[cfg(feature = "unstable-msc4143")]
276    fn test_livekit_rtc_focus_deserialization() {
277        // Given the JSON for a LiveKit RTC focus.
278        let json = json!({
279            "type": "livekit",
280            "livekit_service_url": "https://livekit.example.com"
281        });
282
283        // When deserializing it into an RtcFocusInfo.
284        let focus: RtcFocusInfo = from_json_value(json).unwrap();
285
286        // Then it should be recognized as a LiveKit focus with the correct service URL.
287        assert_let!(RtcFocusInfo::LiveKit(info) = focus);
288        assert_eq!(info.service_url, "https://livekit.example.com");
289    }
290
291    #[test]
292    #[cfg(feature = "unstable-msc4143")]
293    fn test_livekit_rtc_focus_serialization() {
294        // Given a LiveKit RTC focus info.
295        let focus = RtcFocusInfo::livekit("https://livekit.example.com".to_owned());
296
297        // When serializing to JSON, it should match the expected JSON structure.
298        assert_to_canonical_json_eq!(
299            focus,
300            json!({
301                "type": "livekit",
302                "livekit_service_url": "https://livekit.example.com"
303            })
304        );
305    }
306
307    #[test]
308    #[cfg(feature = "unstable-msc4143")]
309    fn test_custom_rtc_focus_serialization() {
310        // Given the JSON for a custom RTC focus type with additional fields.
311        let json = json!({
312            "type": "some-focus-type",
313            "additional-type-specific-field": "https://my_focus.domain",
314            "another-additional-type-specific-field": ["with", "Array", "type"]
315        });
316
317        // When deserializing it into an RtcFocusInfo.
318        let focus: RtcFocusInfo = from_json_value(json.clone()).unwrap();
319
320        // Then it should be recognized as a custom focus type, with all the additional fields
321        // included.
322        assert_eq!(focus.focus_type(), "some-focus-type");
323
324        let data = &focus.data();
325        assert_eq!(data["additional-type-specific-field"], "https://my_focus.domain");
326
327        let array_values: Vec<&str> = data["another-additional-type-specific-field"]
328            .as_array()
329            .unwrap()
330            .iter()
331            .map(|v| v.as_str().unwrap())
332            .collect();
333        assert_eq!(array_values, vec!["with", "Array", "type"]);
334
335        assert!(!data.contains_key("type"));
336
337        // When serializing it back to JSON, it should match the original JSON.
338        assert_to_canonical_json_eq!(focus, json);
339    }
340}