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