1use livekit_protocol as proto;
16use std::sync::Arc;
17use thiserror::Error;
18
19#[derive(Clone, Hash, PartialEq, Eq)]
37pub struct DataTrackSchemaId {
38 inner: Arc<DataTrackSchemaIdInner>,
39}
40
41#[derive(Hash, PartialEq, Eq)]
42struct DataTrackSchemaIdInner {
43 name: String,
44 encoding: DataTrackSchemaEncoding,
45}
46
47impl std::fmt::Debug for DataTrackSchemaId {
48 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
49 f.debug_struct("DataTrackSchemaId")
50 .field("name", &self.inner.name)
51 .field("encoding", &self.inner.encoding)
52 .finish()
53 }
54}
55
56impl DataTrackSchemaId {
57 pub fn new(name: impl Into<String>, encoding: DataTrackSchemaEncoding) -> Self {
59 let inner = DataTrackSchemaIdInner { name: name.into(), encoding }.into();
60 Self { inner }
61 }
62
63 pub fn name(&self) -> &str {
65 &self.inner.name
66 }
67
68 pub fn encoding(&self) -> &DataTrackSchemaEncoding {
70 &self.inner.encoding
71 }
72}
73
74#[non_exhaustive]
83#[derive(Debug, Clone, Hash, PartialEq, Eq)]
84#[cfg_attr(test, derive(fake::Dummy))]
85#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
86pub enum DataTrackSchemaEncoding {
87 Protobuf,
91 Flatbuffer,
95 Ros1Msg,
99 Ros2Msg,
103 Ros2Idl,
107 OmgIdl,
111 JsonSchema,
115
116 Other,
118 Custom(String),
124}
125
126#[non_exhaustive]
134#[derive(Debug, Clone, Hash, PartialEq, Eq)]
135#[cfg_attr(test, derive(fake::Dummy))]
136#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
137pub enum DataTrackFrameEncoding {
138 Ros1,
142 Cdr,
148 Protobuf,
152 Flatbuffer,
156 Cbor,
158 Msgpack,
160 Json,
164
165 Other,
167 Custom(String),
173}
174
175#[derive(Debug, Error, PartialEq)]
177pub enum DataTrackSchemaError {
178 #[error("Frame encoding is required when providing schema ID")]
180 MissingFrameEncoding,
181
182 #[error("Schema ID is required for frame encoding that is not self-describing")]
184 MissingSchemaId,
185
186 #[error("Specified schema and frame encodings are incompatible")]
188 Incompatible,
189}
190
191pub(crate) fn validate_schema(
193 frame_encoding: Option<&DataTrackFrameEncoding>,
194 schema_encoding: Option<&DataTrackSchemaEncoding>,
195) -> Result<(), DataTrackSchemaError> {
196 match (frame_encoding, schema_encoding) {
197 (None, Some(_)) => Err(DataTrackSchemaError::MissingFrameEncoding),
198 (Some(frame_encoding), None) => match frame_encoding.is_self_describing() {
199 Some(false) => Err(DataTrackSchemaError::MissingSchemaId),
200 _ => Ok(()),
201 },
202 (Some(frame_encoding), Some(schema_encoding)) => {
203 match frame_encoding.is_described_by(schema_encoding) {
204 Some(false) => Err(DataTrackSchemaError::Incompatible),
205 _ => Ok(()),
206 }
207 }
208 (None, None) => Ok(()), }
210}
211
212impl DataTrackFrameEncoding {
213 fn is_self_describing(&self) -> Option<bool> {
215 match self {
216 Self::Cbor | Self::Msgpack | Self::Json => Some(true),
217 Self::Other | Self::Custom(_) => None, _ => Some(false),
219 }
220 }
221
222 fn is_described_by(&self, schema_encoding: &DataTrackSchemaEncoding) -> Option<bool> {
224 use DataTrackSchemaEncoding as SchemaEncoding;
225 match (self, schema_encoding) {
226 (Self::Ros1, SchemaEncoding::Ros1Msg)
227 | (Self::Cdr, SchemaEncoding::Ros2Msg)
228 | (Self::Cdr, SchemaEncoding::Ros2Idl)
229 | (Self::Cdr, SchemaEncoding::OmgIdl)
230 | (Self::Protobuf, SchemaEncoding::Protobuf)
231 | (Self::Flatbuffer, SchemaEncoding::Flatbuffer)
232 | (Self::Json, SchemaEncoding::JsonSchema) => Some(true),
233 (Self::Other, _) | (Self::Custom(_), _) => None, _ => Some(false),
235 }
236 }
237}
238
239impl From<proto::DataTrackSchemaId> for DataTrackSchemaId {
240 fn from(msg: proto::DataTrackSchemaId) -> Self {
241 let encoding = msg.encoding.map(Into::into).unwrap_or(DataTrackSchemaEncoding::Other);
242 DataTrackSchemaId::new(msg.name, encoding)
243 }
244}
245
246impl From<DataTrackSchemaId> for proto::DataTrackSchemaId {
247 fn from(value: DataTrackSchemaId) -> Self {
248 Self { name: value.name().to_string(), encoding: Some(value.encoding().clone().into()) }
249 }
250}
251
252impl From<proto::DataTrackSchemaEncoding> for DataTrackSchemaEncoding {
253 fn from(msg: proto::DataTrackSchemaEncoding) -> Self {
254 use proto::data_track_schema_encoding::{Value, WellKnownSchemaEncoding as WellKnown};
255 match msg.value {
256 Some(Value::WellKnown(value)) => match WellKnown::try_from(value) {
257 Ok(WellKnown::Protobuf) => Self::Protobuf,
258 Ok(WellKnown::Flatbuffer) => Self::Flatbuffer,
259 Ok(WellKnown::Ros1Msg) => Self::Ros1Msg,
260 Ok(WellKnown::Ros2Msg) => Self::Ros2Msg,
261 Ok(WellKnown::Ros2Idl) => Self::Ros2Idl,
262 Ok(WellKnown::OmgIdl) => Self::OmgIdl,
263 Ok(WellKnown::JsonSchema) => Self::JsonSchema,
264 Ok(WellKnown::Unspecified) | Err(_) => Self::Other,
266 },
267 Some(Value::Custom(name)) => Self::Custom(name),
268 None => Self::Other,
269 }
270 }
271}
272
273impl From<DataTrackSchemaEncoding> for proto::DataTrackSchemaEncoding {
274 fn from(value: DataTrackSchemaEncoding) -> Self {
275 use proto::data_track_schema_encoding::{Value, WellKnownSchemaEncoding as WellKnown};
276 let well_known = match value {
277 DataTrackSchemaEncoding::Protobuf => WellKnown::Protobuf,
278 DataTrackSchemaEncoding::Flatbuffer => WellKnown::Flatbuffer,
279 DataTrackSchemaEncoding::Ros1Msg => WellKnown::Ros1Msg,
280 DataTrackSchemaEncoding::Ros2Msg => WellKnown::Ros2Msg,
281 DataTrackSchemaEncoding::Ros2Idl => WellKnown::Ros2Idl,
282 DataTrackSchemaEncoding::OmgIdl => WellKnown::OmgIdl,
283 DataTrackSchemaEncoding::JsonSchema => WellKnown::JsonSchema,
284 DataTrackSchemaEncoding::Other => WellKnown::Unspecified,
285 DataTrackSchemaEncoding::Custom(name) => {
286 return Self { value: Some(Value::Custom(name)) }
287 }
288 };
289 Self { value: Some(Value::WellKnown(well_known as i32)) }
290 }
291}
292
293impl From<proto::DataTrackFrameEncoding> for DataTrackFrameEncoding {
294 fn from(msg: proto::DataTrackFrameEncoding) -> Self {
295 use proto::data_track_frame_encoding::{Value, WellKnownFrameEncoding as WellKnown};
296 match msg.value {
297 Some(Value::WellKnown(value)) => match WellKnown::try_from(value) {
298 Ok(WellKnown::Ros1) => Self::Ros1,
299 Ok(WellKnown::Cdr) => Self::Cdr,
300 Ok(WellKnown::Protobuf) => Self::Protobuf,
301 Ok(WellKnown::Flatbuffer) => Self::Flatbuffer,
302 Ok(WellKnown::Cbor) => Self::Cbor,
303 Ok(WellKnown::Msgpack) => Self::Msgpack,
304 Ok(WellKnown::Json) => Self::Json,
305 Ok(WellKnown::Unspecified) | Err(_) => Self::Other,
307 },
308 Some(Value::Custom(name)) => Self::Custom(name),
309 None => Self::Other,
310 }
311 }
312}
313
314impl From<DataTrackFrameEncoding> for proto::DataTrackFrameEncoding {
315 fn from(value: DataTrackFrameEncoding) -> Self {
316 use proto::data_track_frame_encoding::{Value, WellKnownFrameEncoding as WellKnown};
317 let well_known = match value {
318 DataTrackFrameEncoding::Ros1 => WellKnown::Ros1,
319 DataTrackFrameEncoding::Cdr => WellKnown::Cdr,
320 DataTrackFrameEncoding::Protobuf => WellKnown::Protobuf,
321 DataTrackFrameEncoding::Flatbuffer => WellKnown::Flatbuffer,
322 DataTrackFrameEncoding::Cbor => WellKnown::Cbor,
323 DataTrackFrameEncoding::Msgpack => WellKnown::Msgpack,
324 DataTrackFrameEncoding::Json => WellKnown::Json,
325 DataTrackFrameEncoding::Other => WellKnown::Unspecified,
326 DataTrackFrameEncoding::Custom(name) => {
327 return Self { value: Some(Value::Custom(name)) }
328 }
329 };
330 Self { value: Some(Value::WellKnown(well_known as i32)) }
331 }
332}
333
334impl From<DataTrackSchemaId> for proto::DataBlobKey {
335 fn from(id: DataTrackSchemaId) -> Self {
336 Self { key: Some(proto::data_blob_key::Key::SchemaId(id.into())) }
337 }
338}
339
340#[cfg(test)]
341impl fake::Dummy<fake::Faker> for DataTrackSchemaId {
342 fn dummy_with_rng<R: rand::Rng + ?Sized>(_: &fake::Faker, rng: &mut R) -> Self {
343 use fake::{Fake, Faker};
344 let name: String = Faker.fake_with_rng(rng);
345 let encoding: DataTrackSchemaEncoding = Faker.fake_with_rng(rng);
346 Self::new(name, encoding)
347 }
348}
349
350#[cfg(test)]
351mod tests {
352 use super::*;
353
354 #[test]
355 fn test_validate_schema_not_specified() {
356 assert_eq!(validate_schema(None, None), Ok(()));
357 }
358
359 #[test]
360 fn test_validate_schema_self_describing() {
361 assert_eq!(validate_schema(Some(&DataTrackFrameEncoding::Json), None), Ok(()));
362 }
363
364 #[test]
365 fn test_validate_schema_compatible_encodings() {
366 assert_eq!(
367 validate_schema(
368 Some(&DataTrackFrameEncoding::Cdr),
369 Some(&DataTrackSchemaEncoding::Ros2Idl)
370 ),
371 Ok(())
372 );
373 }
374
375 #[test]
376 fn test_validate_schema_custom() {
377 assert_eq!(
378 validate_schema(
379 Some(&DataTrackFrameEncoding::Custom("my-frame-encoding".to_string())),
380 Some(&DataTrackSchemaEncoding::Custom("my-schema-encoding".to_string()))
381 ),
382 Ok(())
383 );
384 }
385
386 #[test]
387 fn test_validate_schema_missing_frame_encoding() {
388 assert_eq!(
389 validate_schema(None, Some(&DataTrackSchemaEncoding::Protobuf)),
390 Err(DataTrackSchemaError::MissingFrameEncoding)
391 );
392 }
393
394 #[test]
395 fn test_validate_schema_missing_schema_id() {
396 assert_eq!(
397 validate_schema(Some(&DataTrackFrameEncoding::Protobuf), None),
398 Err(DataTrackSchemaError::MissingSchemaId)
399 );
400 }
401
402 #[test]
403 fn test_validate_schema_incompatible() {
404 assert_eq!(
405 validate_schema(
406 Some(&DataTrackFrameEncoding::Json),
407 Some(&DataTrackSchemaEncoding::Protobuf)
408 ),
409 Err(DataTrackSchemaError::Incompatible)
410 );
411 }
412}