1use std::borrow::Cow;
6
7use ruma_common::serde::JsonObject;
8use serde::{Deserialize, Deserializer, Serialize};
9
10#[cfg(feature = "unstable-msc4195")]
11pub mod livekit;
12pub mod transports;
13
14#[derive(Clone, Debug, Serialize, PartialEq, Eq)]
16#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
17#[serde(tag = "type")]
18pub enum RtcTransport {
19 #[cfg(feature = "unstable-msc4195")]
21 #[serde(rename = "livekit")]
22 LiveKit(LiveKitRtcTransport),
23
24 #[doc(hidden)]
26 #[serde(untagged)]
27 _Custom(CustomRtcTransport),
28}
29
30impl<'de> Deserialize<'de> for RtcTransport {
31 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
32 where
33 D: Deserializer<'de>,
34 {
35 use serde::de::Error;
36
37 let mut obj = JsonObject::deserialize(deserializer)?;
38 let transport_type = match obj.remove("type") {
39 Some(serde_json::Value::String(s)) => s,
40 Some(_) => return Err(D::Error::custom("`type` must be a string")),
41 None => return Err(D::Error::missing_field("type")),
42 };
43
44 Ok(match transport_type.as_str() {
45 #[cfg(feature = "unstable-msc4195")]
46 "livekit" => Self::LiveKit(
47 serde_json::from_value(serde_json::Value::Object(obj)).map_err(D::Error::custom)?,
48 ),
49 _ => Self::_Custom(CustomRtcTransport { transport_type, data: obj }),
50 })
51 }
52}
53
54impl RtcTransport {
55 pub fn new(transport_type: &str, data: JsonObject) -> serde_json::Result<Self> {
66 Ok(match transport_type {
67 #[cfg(feature = "unstable-msc4195")]
68 "livekit" => Self::LiveKit(serde_json::from_value(serde_json::Value::Object(data))?),
69 _ => Self::_Custom(CustomRtcTransport {
70 transport_type: transport_type.to_owned(),
71 data,
72 }),
73 })
74 }
75
76 #[cfg(feature = "unstable-msc4195")]
77 pub fn livekit(url: String) -> Self {
79 Self::LiveKit(LiveKitRtcTransport::new(url))
80 }
81
82 pub fn transport_type(&self) -> &str {
84 match self {
85 #[cfg(feature = "unstable-msc4195")]
86 Self::LiveKit(_) => "livekit",
87 Self::_Custom(custom) => &custom.transport_type,
88 }
89 }
90
91 pub fn data(&self) -> Cow<'_, JsonObject> {
99 #[cfg(feature = "unstable-msc4195")]
100 fn serialize<T: Serialize>(object: &T) -> JsonObject {
101 use serde_json::Value as JsonValue;
102
103 match serde_json::to_value(object).expect("rtc transport type serialization to succeed")
104 {
105 JsonValue::Object(object) => object,
106 _ => panic!("all rtc transport types must serialize to objects"),
107 }
108 }
109
110 match self {
111 #[cfg(feature = "unstable-msc4195")]
112 Self::LiveKit(info) => Cow::Owned(serialize(info)),
113 Self::_Custom(info) => Cow::Borrowed(&info.data),
114 }
115 }
116}
117
118#[cfg(feature = "unstable-msc4195")]
120#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
121#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
122pub struct LiveKitRtcTransport {
123 #[serde(skip_serializing_if = "Option::is_none")]
125 pub url: Option<String>,
126
127 #[serde(rename = "livekit_service_url", skip_serializing_if = "Option::is_none")]
129 #[deprecated = "Use `url` instead unless you require backwards compatibility with older versions of MSC4195."]
130 pub service_url: Option<String>,
131}
132
133#[cfg(feature = "unstable-msc4195")]
134impl LiveKitRtcTransport {
135 #[allow(deprecated)]
137 pub fn new(url: String) -> Self {
138 Self { url: Some(url), service_url: None }
139 }
140
141 #[deprecated = "Use `LiveKitRtcTransport::new()` instead unless you require backwards compatibility with older versions of MSC4195."]
143 #[allow(deprecated)]
144 pub fn with_service_url(service_url: String) -> Self {
145 Self { url: None, service_url: Some(service_url) }
146 }
147}
148
149#[cfg(feature = "unstable-msc4195")]
150impl From<LiveKitRtcTransport> for RtcTransport {
151 fn from(value: LiveKitRtcTransport) -> Self {
152 Self::LiveKit(value)
153 }
154}
155
156#[doc(hidden)]
162#[derive(Clone, Debug, Serialize, PartialEq, Eq)]
163pub struct CustomRtcTransport {
164 #[serde(rename = "type")]
166 transport_type: String,
167
168 #[serde(flatten)]
170 data: JsonObject,
171}
172
173#[cfg(test)]
174mod tests {
175 use serde_json::{
176 Value as JsonValue, from_value as from_json_value, json, to_value as to_json_value,
177 };
178 use strass::assert_let;
179
180 use super::{LiveKitRtcTransport, RtcTransport};
181
182 #[test]
183 fn serialize_roundtrip_custom_rtc_transport() {
184 let transport_type = "local.custom.transport";
185 assert_let!(
186 JsonValue::Object(transport_data) = json!({
187 "foo": "bar",
188 "baz": true,
189 })
190 );
191 let transport = RtcTransport::new(transport_type, transport_data.clone()).unwrap();
192 let json = json!({
193 "type": transport_type,
194 "foo": "bar",
195 "baz": true,
196 });
197
198 assert_eq!(transport.transport_type(), transport_type);
199 assert_eq!(*transport.data().as_ref(), transport_data);
200 assert_eq!(to_json_value(&transport).unwrap(), json);
201 assert_eq!(from_json_value::<RtcTransport>(json).unwrap(), transport);
202 }
203
204 #[cfg(feature = "unstable-msc4195")]
205 #[test]
206 fn livekit_transport_new_and_from_impl() {
207 use super::LiveKitRtcTransport;
208
209 let url = "wss://livekit.local/".to_owned();
210 let inner = LiveKitRtcTransport::new(url.clone());
211 let transport = RtcTransport::from(inner);
212 assert_eq!(transport.transport_type(), "livekit");
213 assert_eq!(transport, RtcTransport::livekit(url));
214 }
215
216 #[cfg(feature = "unstable-msc4195")]
217 #[test]
218 fn serialize_roundtrip_livekit_sfu_transport() {
219 let transport_type = "livekit";
220 let url = "wss://livekit.local/";
221 let transport = RtcTransport::livekit(url.to_owned());
222 let json = json!({
223 "type": transport_type,
224 "url": url,
225 });
226
227 assert_eq!(transport.transport_type(), transport_type);
228 assert_eq!(to_json_value(&transport).unwrap(), json);
229 assert_eq!(from_json_value::<RtcTransport>(json).unwrap(), transport);
230 }
231
232 #[cfg(feature = "unstable-msc4195")]
233 #[test]
234 #[allow(deprecated)]
235 fn serialize_roundtrip_legacy_livekit_sfu_transport() {
236 let livekit_service_url = "https://livekit-jwt.local/";
237 let json = json!({
238 "type": "livekit",
239 "livekit_service_url": livekit_service_url,
240 });
241
242 assert_let!(Ok(RtcTransport::LiveKit(livekit)) = from_json_value(json.clone()));
243 assert_eq!(livekit.url, None);
244 assert_eq!(livekit.service_url.as_deref(), Some(livekit_service_url));
245 assert_eq!(livekit, LiveKitRtcTransport::with_service_url(livekit_service_url.to_owned()));
246 assert_eq!(to_json_value(RtcTransport::LiveKit(livekit)).unwrap(), json);
247 }
248
249 #[cfg(feature = "unstable-msc4195")]
250 #[test]
251 #[allow(deprecated)]
252 fn serialize_roundtrip_livekit_sfu_transport_with_legacy_field() {
253 let url = "wss://livekit.local/";
254 let livekit_service_url = "https://livekit-jwt.local/";
255 let json = json!({
256 "type": "livekit",
257 "url": url,
258 "livekit_service_url": livekit_service_url,
259 });
260
261 assert_let!(Ok(RtcTransport::LiveKit(livekit)) = from_json_value(json.clone()));
262 assert_eq!(livekit.url.as_deref(), Some(url));
263 assert_eq!(livekit.service_url.as_deref(), Some(livekit_service_url));
264 assert_eq!(to_json_value(RtcTransport::LiveKit(livekit)).unwrap(), json);
265 }
266}