ruma_client_api/discovery/
discover_homeserver.rs

1//! `GET /.well-known/matrix/client` ([spec])
2//!
3//! [spec]: https://spec.matrix.org/latest/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;
12use ruma_common::{
13    api::{request, response, Metadata},
14    metadata,
15};
16#[cfg(feature = "unstable-msc4143")]
17use serde::de::DeserializeOwned;
18use serde::{Deserialize, Serialize};
19#[cfg(feature = "unstable-msc4143")]
20use serde_json::Value as JsonValue;
21
22const METADATA: Metadata = metadata! {
23    method: GET,
24    rate_limited: false,
25    authentication: None,
26    history: {
27        1.0 => "/.well-known/matrix/client",
28    }
29};
30
31/// Request type for the `client_well_known` endpoint.
32#[request(error = crate::Error)]
33#[derive(Default)]
34pub struct Request {}
35
36/// Response type for the `client_well_known` endpoint.
37#[response(error = crate::Error)]
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, Deserialize, 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/// Information about a LiveKit RTC focus.
212#[cfg(feature = "unstable-msc4143")]
213#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
214#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
215pub struct LiveKitRtcFocusInfo {
216    /// The URL for the LiveKit service.
217    #[serde(rename = "livekit_service_url")]
218    pub service_url: String,
219}
220
221/// Information about a custom RTC focus type.
222#[doc(hidden)]
223#[cfg(feature = "unstable-msc4143")]
224#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
225pub struct CustomRtcFocusInfo {
226    /// The type of RTC focus.
227    #[serde(rename = "type")]
228    focus_type: String,
229
230    /// Remaining RTC focus data.
231    #[serde(flatten)]
232    data: JsonObject,
233}
234
235#[cfg(test)]
236mod tests {
237    #[cfg(feature = "unstable-msc4143")]
238    use assert_matches2::assert_matches;
239    #[cfg(feature = "unstable-msc4143")]
240    use serde_json::{from_value as from_json_value, json, to_value as to_json_value};
241
242    #[cfg(feature = "unstable-msc4143")]
243    use super::RtcFocusInfo;
244
245    #[test]
246    #[cfg(feature = "unstable-msc4143")]
247    fn test_livekit_rtc_focus_deserialization() {
248        // Given the JSON for a LiveKit RTC focus.
249        let json = json!({
250            "type": "livekit",
251            "livekit_service_url": "https://livekit.example.com"
252        });
253
254        // When deserializing it into an RtcFocusInfo.
255        let focus: RtcFocusInfo = from_json_value(json).unwrap();
256
257        // Then it should be recognized as a LiveKit focus with the correct service URL.
258        assert_matches!(focus, RtcFocusInfo::LiveKit(info));
259        assert_eq!(info.service_url, "https://livekit.example.com");
260    }
261
262    #[test]
263    #[cfg(feature = "unstable-msc4143")]
264    fn test_livekit_rtc_focus_serialization() {
265        // Given a LiveKit RTC focus info.
266        let focus = RtcFocusInfo::livekit("https://livekit.example.com".to_owned());
267
268        // When serializing it to JSON.
269        let json = to_json_value(&focus).unwrap();
270
271        // Then it should match the expected JSON structure.
272        assert_eq!(
273            json,
274            json!({
275                "type": "livekit",
276                "livekit_service_url": "https://livekit.example.com"
277            })
278        );
279    }
280
281    #[test]
282    #[cfg(feature = "unstable-msc4143")]
283    fn test_custom_rtc_focus_serialization() {
284        // Given the JSON for a custom RTC focus type with additional fields.
285        let json = json!({
286            "type": "some-focus-type",
287            "additional-type-specific-field": "https://my_focus.domain",
288            "another-additional-type-specific-field": ["with", "Array", "type"]
289        });
290
291        // When deserializing it into an RtcFocusInfo.
292        let focus: RtcFocusInfo = from_json_value(json.clone()).unwrap();
293
294        // Then it should be recognized as a custom focus type, with all the additional fields
295        // included.
296        assert_eq!(focus.focus_type(), "some-focus-type");
297
298        let data = &focus.data();
299        assert_eq!(data["additional-type-specific-field"], "https://my_focus.domain");
300
301        let array_values: Vec<&str> = data["another-additional-type-specific-field"]
302            .as_array()
303            .unwrap()
304            .iter()
305            .map(|v| v.as_str().unwrap())
306            .collect();
307        assert_eq!(array_values, vec!["with", "Array", "type"]);
308
309        assert!(!data.contains_key("type"));
310
311        // When serializing it back to JSON.
312        let serialized = to_json_value(&focus).unwrap();
313
314        // Then it should match the original JSON.
315        assert_eq!(serialized, json);
316    }
317}