Skip to main content

ruma_client_api/
rtc.rs

1//! [MatrixRTC] endpoints.
2//!
3//! [MatrixRTC]: https://github.com/matrix-org/matrix-spec-proposals/pull/4143
4
5use std::borrow::Cow;
6
7use ruma_common::serde::JsonObject;
8use serde::{Deserialize, Deserializer, Serialize};
9
10pub mod transports;
11
12/// Information about a specific MatrixRTC transport.
13#[derive(Clone, Debug, Serialize, PartialEq, Eq)]
14#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
15#[serde(tag = "type")]
16pub enum RtcTransport {
17    /// A LiveKit RTC transport.
18    #[cfg(feature = "unstable-msc4195")]
19    #[serde(rename = "livekit")]
20    LiveKit(LiveKitRtcTransport),
21
22    /// A custom RTC transport.
23    #[doc(hidden)]
24    #[serde(untagged)]
25    _Custom(CustomRtcTransport),
26}
27
28impl<'de> Deserialize<'de> for RtcTransport {
29    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
30    where
31        D: Deserializer<'de>,
32    {
33        use serde::de::Error;
34
35        let mut obj = JsonObject::deserialize(deserializer)?;
36        let transport_type = match obj.remove("type") {
37            Some(serde_json::Value::String(s)) => s,
38            Some(_) => return Err(D::Error::custom("`type` must be a string")),
39            None => return Err(D::Error::missing_field("type")),
40        };
41
42        Ok(match transport_type.as_str() {
43            #[cfg(feature = "unstable-msc4195")]
44            "livekit" => Self::LiveKit(
45                serde_json::from_value(serde_json::Value::Object(obj)).map_err(D::Error::custom)?,
46            ),
47            _ => Self::_Custom(CustomRtcTransport { transport_type, data: obj }),
48        })
49    }
50}
51
52impl RtcTransport {
53    /// A constructor to create a custom RTC transport.
54    ///
55    /// Prefer to use the public variants of `RtcTransport` where possible; this constructor is
56    /// meant to be used for unsupported transport types only and does not allow setting arbitrary
57    /// data for supported ones.
58    ///
59    /// # Errors
60    ///
61    /// Returns an error if the `transport_type` is known and serialization of `data` to the
62    /// corresponding `RtcTransport` variant fails.
63    pub fn new(transport_type: &str, data: JsonObject) -> serde_json::Result<Self> {
64        Ok(match transport_type {
65            #[cfg(feature = "unstable-msc4195")]
66            "livekit" => Self::LiveKit(serde_json::from_value(serde_json::Value::Object(data))?),
67            _ => Self::_Custom(CustomRtcTransport {
68                transport_type: transport_type.to_owned(),
69                data,
70            }),
71        })
72    }
73
74    #[cfg(feature = "unstable-msc4195")]
75    /// Creates a new `RtcTransport::LiveKit`.
76    pub fn livekit(service_url: String) -> Self {
77        Self::LiveKit(LiveKitRtcTransport { service_url })
78    }
79
80    /// Returns a reference to the transport type of this RTC transport.
81    pub fn transport_type(&self) -> &str {
82        match self {
83            #[cfg(feature = "unstable-msc4195")]
84            Self::LiveKit(_) => "livekit",
85            Self::_Custom(custom) => &custom.transport_type,
86        }
87    }
88
89    /// Returns the associated data.
90    ///
91    /// The returned JSON object won't contain the `type` field, please use
92    /// [`.transport_type()`][Self::transport_type] to access that.
93    ///
94    /// Prefer to use the public variants of `RtcTransport` where possible; this method is meant
95    /// to be used for custom transport types only.
96    pub fn data(&self) -> Cow<'_, JsonObject> {
97        #[cfg(feature = "unstable-msc4195")]
98        fn serialize<T: Serialize>(object: &T) -> JsonObject {
99            use serde_json::Value as JsonValue;
100
101            match serde_json::to_value(object).expect("rtc transport type serialization to succeed")
102            {
103                JsonValue::Object(object) => object,
104                _ => panic!("all rtc transport types must serialize to objects"),
105            }
106        }
107
108        match self {
109            #[cfg(feature = "unstable-msc4195")]
110            Self::LiveKit(info) => Cow::Owned(serialize(info)),
111            Self::_Custom(info) => Cow::Borrowed(&info.data),
112        }
113    }
114}
115
116/// Information about a LiveKit RTC transport.
117#[cfg(feature = "unstable-msc4195")]
118#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
119#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
120pub struct LiveKitRtcTransport {
121    /// The URL for the LiveKit service.
122    #[serde(rename = "livekit_service_url")]
123    pub service_url: String,
124}
125
126#[cfg(feature = "unstable-msc4195")]
127impl LiveKitRtcTransport {
128    /// Creates a new `LiveKitRtcTransport` with the given service URL.
129    pub fn new(service_url: String) -> Self {
130        Self { service_url }
131    }
132}
133
134#[cfg(feature = "unstable-msc4195")]
135impl From<LiveKitRtcTransport> for RtcTransport {
136    fn from(value: LiveKitRtcTransport) -> Self {
137        Self::LiveKit(value)
138    }
139}
140
141/// Information about a custom RTC transport.
142///
143/// This type does not implement `Deserialize` to prevent users from
144/// constructing the `_Custom` variant of [`RtcTransport`] for a known `type`.
145/// Deserialize through [`RtcTransport`] instead.
146#[doc(hidden)]
147#[derive(Clone, Debug, Serialize, PartialEq, Eq)]
148pub struct CustomRtcTransport {
149    /// The type of RTC transport.
150    #[serde(rename = "type")]
151    transport_type: String,
152
153    /// Remaining RTC transport data.
154    #[serde(flatten)]
155    data: JsonObject,
156}
157
158#[cfg(test)]
159mod tests {
160    use assert_matches2::assert_matches;
161    use serde_json::{
162        Value as JsonValue, from_value as from_json_value, json, to_value as to_json_value,
163    };
164
165    use super::RtcTransport;
166
167    #[test]
168    fn serialize_roundtrip_custom_rtc_transport() {
169        let transport_type = "local.custom.transport";
170        assert_matches!(
171            json!({
172                "foo": "bar",
173                "baz": true,
174            }),
175            JsonValue::Object(transport_data)
176        );
177        let transport = RtcTransport::new(transport_type, transport_data.clone()).unwrap();
178        let json = json!({
179            "type": transport_type,
180            "foo": "bar",
181            "baz": true,
182        });
183
184        assert_eq!(transport.transport_type(), transport_type);
185        assert_eq!(*transport.data().as_ref(), transport_data);
186        assert_eq!(to_json_value(&transport).unwrap(), json);
187        assert_eq!(from_json_value::<RtcTransport>(json).unwrap(), transport);
188    }
189
190    #[cfg(feature = "unstable-msc4195")]
191    #[test]
192    fn livekit_transport_new_and_from_impl() {
193        use super::LiveKitRtcTransport;
194
195        let url = "http://livekit.local/".to_owned();
196        let inner = LiveKitRtcTransport::new(url.clone());
197        let transport = RtcTransport::from(inner);
198        assert_eq!(transport.transport_type(), "livekit");
199        assert_eq!(transport, RtcTransport::livekit(url));
200    }
201
202    #[cfg(feature = "unstable-msc4195")]
203    #[test]
204    fn serialize_roundtrip_livekit_sfu_transport() {
205        let transport_type = "livekit";
206        let livekit_service_url = "http://livekit.local/";
207        let transport = RtcTransport::livekit(livekit_service_url.to_owned());
208        let json = json!({
209            "type": transport_type,
210            "livekit_service_url": livekit_service_url,
211        });
212
213        assert_eq!(transport.transport_type(), transport_type);
214        assert_eq!(to_json_value(&transport).unwrap(), json);
215        assert_eq!(from_json_value::<RtcTransport>(json).unwrap(), transport);
216    }
217}