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;
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, de::DeserializeOwned};
19#[cfg(feature = "unstable-msc4143")]
20use serde_json::Value as JsonValue;
21
22metadata! {
23    method: GET,
24    rate_limited: false,
25    authentication: NoAccessToken,
26    path: "/.well-known/matrix/client",
27}
28
29/// Request type for the `client_well_known` endpoint.
30#[request]
31#[derive(Default)]
32pub struct Request {}
33
34/// Response type for the `client_well_known` endpoint.
35#[response]
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, 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#[cfg(feature = "unstable-msc4143")]
210impl<'de> Deserialize<'de> for RtcFocusInfo {
211    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
212    where
213        D: Deserializer<'de>,
214    {
215        use as_variant::as_variant;
216
217        let mut data = JsonObject::deserialize(deserializer)?;
218        let focus_type = data
219            .remove("type")
220            .and_then(|value| as_variant!(value, JsonValue::String))
221            .ok_or_else(|| de::Error::missing_field("type"))?;
222
223        match focus_type.as_ref() {
224            "livekit" => serde_json::from_value(data.into()).map(Self::LiveKit),
225            _ => Ok(Self::_Custom(CustomRtcFocusInfo { focus_type, data })),
226        }
227        .map_err(de::Error::custom)
228    }
229}
230
231/// Information about a LiveKit RTC focus.
232#[cfg(feature = "unstable-msc4143")]
233#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
234#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
235pub struct LiveKitRtcFocusInfo {
236    /// The URL for the LiveKit service.
237    #[serde(rename = "livekit_service_url")]
238    pub service_url: String,
239}
240
241/// Information about a custom RTC focus type.
242#[doc(hidden)]
243#[cfg(feature = "unstable-msc4143")]
244#[derive(Clone, Debug, Serialize, PartialEq, Eq)]
245pub struct CustomRtcFocusInfo {
246    /// The type of RTC focus.
247    #[serde(rename = "type")]
248    focus_type: String,
249
250    /// Remaining RTC focus data.
251    #[serde(flatten)]
252    data: JsonObject,
253}
254
255#[cfg(test)]
256mod tests {
257    #[cfg(feature = "unstable-msc4143")]
258    use assert_matches2::assert_let;
259    #[cfg(feature = "unstable-msc4143")]
260    use ruma_common::canonical_json::assert_to_canonical_json_eq;
261    #[cfg(feature = "unstable-msc4143")]
262    use serde_json::{from_value as from_json_value, json};
263
264    #[cfg(feature = "unstable-msc4143")]
265    use super::RtcFocusInfo;
266
267    #[test]
268    #[cfg(feature = "unstable-msc4143")]
269    fn test_livekit_rtc_focus_deserialization() {
270        // Given the JSON for a LiveKit RTC focus.
271        let json = json!({
272            "type": "livekit",
273            "livekit_service_url": "https://livekit.example.com"
274        });
275
276        // When deserializing it into an RtcFocusInfo.
277        let focus: RtcFocusInfo = from_json_value(json).unwrap();
278
279        // Then it should be recognized as a LiveKit focus with the correct service URL.
280        assert_let!(RtcFocusInfo::LiveKit(info) = focus);
281        assert_eq!(info.service_url, "https://livekit.example.com");
282    }
283
284    #[test]
285    #[cfg(feature = "unstable-msc4143")]
286    fn test_livekit_rtc_focus_serialization() {
287        // Given a LiveKit RTC focus info.
288        let focus = RtcFocusInfo::livekit("https://livekit.example.com".to_owned());
289
290        // When serializing to JSON, it should match the expected JSON structure.
291        assert_to_canonical_json_eq!(
292            focus,
293            json!({
294                "type": "livekit",
295                "livekit_service_url": "https://livekit.example.com"
296            })
297        );
298    }
299
300    #[test]
301    #[cfg(feature = "unstable-msc4143")]
302    fn test_custom_rtc_focus_serialization() {
303        // Given the JSON for a custom RTC focus type with additional fields.
304        let json = json!({
305            "type": "some-focus-type",
306            "additional-type-specific-field": "https://my_focus.domain",
307            "another-additional-type-specific-field": ["with", "Array", "type"]
308        });
309
310        // When deserializing it into an RtcFocusInfo.
311        let focus: RtcFocusInfo = from_json_value(json.clone()).unwrap();
312
313        // Then it should be recognized as a custom focus type, with all the additional fields
314        // included.
315        assert_eq!(focus.focus_type(), "some-focus-type");
316
317        let data = &focus.data();
318        assert_eq!(data["additional-type-specific-field"], "https://my_focus.domain");
319
320        let array_values: Vec<&str> = data["another-additional-type-specific-field"]
321            .as_array()
322            .unwrap()
323            .iter()
324            .map(|v| v.as_str().unwrap())
325            .collect();
326        assert_eq!(array_values, vec!["with", "Array", "type"]);
327
328        assert!(!data.contains_key("type"));
329
330        // When serializing it back to JSON, it should match the original JSON.
331        assert_to_canonical_json_eq!(focus, json);
332    }
333}