1use std::time::Duration;
8
9use crate::{
10 Element, Endpoint, Fid, Member, Ping, Pong, Room,
11 control::{ParseFidError, Request},
12 endpoint::{
13 self, web_rtc_play,
14 web_rtc_play::WebRtcPlay,
15 web_rtc_publish::{
16 self, AudioSettings, P2pMode, Policy, VideoSettings, WebRtcPublish,
17 },
18 },
19 grpc::{CallbackUrlParseError, api as proto, convert::ProtobufError},
20 member::{self, Credentials},
21 room,
22};
23
24impl TryFrom<(Fid, Element)> for proto::Element {
25 type Error = ProtobufError;
26
27 fn try_from((fid, spec): (Fid, Element)) -> Result<Self, Self::Error> {
28 use proto::element::El;
29
30 let el = match (fid, spec) {
31 (Fid::Room { id }, Element::Room(room)) => {
32 Ok(El::Room(Room { id, spec: room.spec }.into()))
33 }
34 (Fid::Member { id, .. }, Element::Member(member)) => {
35 Ok(El::Member(Member { id, spec: member.spec }.into()))
36 }
37 (Fid::Endpoint { id, .. }, Element::Endpoint(endpoint)) => {
38 Ok(Endpoint { id, spec: endpoint.spec }.into())
39 }
40 (id @ Fid::Room { .. }, _) => Err(ProtobufError::ExpectedElement(
41 "Room",
42 id.to_string().into(),
43 )),
44 (id @ Fid::Member { .. }, _) => Err(
45 ProtobufError::ExpectedElement("Member", id.to_string().into()),
46 ),
47
48 (id @ Fid::Endpoint { .. }, _) => {
49 Err(ProtobufError::ExpectedElement(
50 "Endpoint",
51 id.to_string().into(),
52 ))
53 }
54 }?;
55
56 Ok(Self { el: Some(el) })
57 }
58}
59
60impl TryFrom<proto::CreateRequest> for Request {
61 type Error = ProtobufError;
62
63 fn try_from(req: proto::CreateRequest) -> Result<Self, Self::Error> {
64 let parent_fid = req.parent_fid;
65 let el = req.el.ok_or_else(|| {
66 ProtobufError::NoElementForId(parent_fid.as_str().into())
67 })?;
68
69 if parent_fid.is_empty() {
70 return Room::try_from(el)
71 .map(|room| Self::Room { id: room.id, spec: room.spec });
72 }
73
74 match parent_fid.parse::<Fid>()? {
75 Fid::Room { id: room_id } => {
76 Member::try_from(el).map(|member| Self::Member {
77 id: member.id,
78 room_id,
79 spec: Box::new(member.spec),
80 })
81 }
82 Fid::Member { id: member_id, room_id } => Endpoint::try_from(el)
83 .map(|endpoint| Self::Endpoint {
84 id: endpoint.id,
85 room_id,
86 member_id,
87 spec: endpoint.spec,
88 }),
89 Fid::Endpoint { .. } => Err(ProtobufError::ParseFidErr(
90 ParseFidError::TooManyPaths(parent_fid.into()),
91 )),
92 }
93 }
94}
95
96impl TryFrom<proto::ApplyRequest> for Request {
97 type Error = ProtobufError;
98
99 fn try_from(req: proto::ApplyRequest) -> Result<Self, Self::Error> {
100 let parent_fid = req.parent_fid;
101 let el = req.el.ok_or_else(|| {
102 ProtobufError::NoElementForId(parent_fid.as_str().into())
103 })?;
104
105 match parent_fid.parse::<Fid>()? {
106 Fid::Room { .. } => Room::try_from(el)
107 .map(|room| Self::Room { id: room.id, spec: room.spec }),
108 Fid::Member { room_id, .. } => {
109 Member::try_from(el).map(|member| Self::Member {
110 id: member.id,
111 room_id,
112 spec: Box::new(member.spec),
113 })
114 }
115 Fid::Endpoint { .. } => Err(ProtobufError::Unimplemented),
116 }
117 }
118}
119
120impl From<Request> for proto::CreateRequest {
121 fn from(req: Request) -> Self {
122 let (parent_fid, el) = match req {
123 Request::Room { id, spec } => {
124 (String::new(), Room { id, spec }.into())
125 }
126 Request::Member { id, room_id, spec } => (
127 Fid::Room { id: room_id }.to_string(),
128 Member { id, spec: *spec }.into(),
129 ),
130 Request::Endpoint { id, room_id, member_id, spec } => (
131 Fid::Member { id: member_id, room_id }.to_string(),
132 Endpoint { id, spec }.into(),
133 ),
134 };
135
136 Self { parent_fid, el: Some(el) }
137 }
138}
139
140impl From<Request> for proto::ApplyRequest {
141 fn from(req: Request) -> Self {
142 let (parent_fid, el) = match req {
143 Request::Room { id, spec } => {
144 (Fid::Room { id: id.clone() }, Room { id, spec }.into())
145 }
146 Request::Member { id, room_id, spec } => (
147 Fid::Member { id: id.clone(), room_id },
148 Member { id, spec: *spec }.into(),
149 ),
150 Request::Endpoint { id, room_id, member_id, spec } => (
151 Fid::Endpoint { id: id.clone(), room_id, member_id },
152 Endpoint { id, spec }.into(),
153 ),
154 };
155
156 Self { parent_fid: parent_fid.to_string(), el: Some(el) }
157 }
158}
159
160impl TryFrom<proto::Element> for Element {
161 type Error = ProtobufError;
162
163 fn try_from(el: proto::Element) -> Result<Self, Self::Error> {
164 use proto::element::El;
165
166 Ok(match el.el.ok_or(ProtobufError::NoElement)? {
167 El::Member(member) => {
168 Self::Member(Box::new(Member::try_from(member)?))
169 }
170 El::Room(room) => Self::Room(Room::try_from(room)?),
171 El::WebrtcPlay(play) => {
172 Self::Endpoint(WebRtcPlay::try_from(play)?.into())
173 }
174 El::WebrtcPub(publish) => {
175 Self::Endpoint(WebRtcPublish::try_from(publish)?.into())
176 }
177 })
178 }
179}
180
181impl TryFrom<proto::create_request::El> for Room {
182 type Error = ProtobufError;
183
184 fn try_from(val: proto::create_request::El) -> Result<Self, Self::Error> {
185 use proto::create_request::El;
186
187 match val {
188 El::Room(room) => room.try_into(),
189 El::Member(proto::Member { id, .. })
190 | El::WebrtcPub(proto::WebRtcPublishEndpoint { id, .. })
191 | El::WebrtcPlay(proto::WebRtcPlayEndpoint { id, .. }) => {
192 Err(ProtobufError::ExpectedElement("Room", id.into()))
193 }
194 }
195 }
196}
197
198impl TryFrom<proto::apply_request::El> for Room {
199 type Error = ProtobufError;
200
201 fn try_from(val: proto::apply_request::El) -> Result<Self, Self::Error> {
202 use proto::apply_request::El;
203
204 match val {
205 El::Room(room) => room.try_into(),
206 El::Member(proto::Member { id, .. })
207 | El::WebrtcPub(proto::WebRtcPublishEndpoint { id, .. })
208 | El::WebrtcPlay(proto::WebRtcPlayEndpoint { id, .. }) => {
209 Err(ProtobufError::ExpectedElement("Room", id.into()))
210 }
211 }
212 }
213}
214
215impl TryFrom<proto::Room> for Room {
216 type Error = ProtobufError;
217
218 fn try_from(room: proto::Room) -> Result<Self, Self::Error> {
219 Ok(Self {
220 id: room.id.into(),
221 spec: room::Spec {
222 pipeline: room
223 .pipeline
224 .into_iter()
225 .map(|(id, room_el)| {
226 room_el.el.map_or(
227 Err(ProtobufError::NoElementForId(id.into())),
228 |el| {
229 Member::try_from(el)
230 .map(|m| (m.id, m.spec.into()))
231 },
232 )
233 })
234 .collect::<Result<_, _>>()?,
235 },
236 })
237 }
238}
239
240impl From<Room> for proto::create_request::El {
241 fn from(room: Room) -> Self {
242 Self::Room(room.into())
243 }
244}
245
246impl From<Room> for proto::apply_request::El {
247 fn from(room: Room) -> Self {
248 Self::Room(room.into())
249 }
250}
251
252impl From<Room> for proto::Room {
253 fn from(room: Room) -> Self {
254 Self {
255 id: room.id.into(),
256 pipeline: room
257 .spec
258 .pipeline
259 .into_iter()
260 .map(|(id, el)| match el {
261 room::PipelineSpec::Member(spec) => {
262 (id.clone().into(), Member { id, spec }.into())
263 }
264 })
265 .collect(),
266 }
267 }
268}
269
270impl TryFrom<proto::create_request::El> for Member {
271 type Error = ProtobufError;
272
273 fn try_from(val: proto::create_request::El) -> Result<Self, Self::Error> {
274 use proto::create_request::El;
275
276 match val {
277 El::Member(member) => member.try_into(),
278 El::Room(proto::Room { id, .. })
279 | El::WebrtcPub(proto::WebRtcPublishEndpoint { id, .. })
280 | El::WebrtcPlay(proto::WebRtcPlayEndpoint { id, .. }) => {
281 Err(ProtobufError::ExpectedElement("Member", id.into()))
282 }
283 }
284 }
285}
286
287impl TryFrom<proto::apply_request::El> for Member {
288 type Error = ProtobufError;
289
290 fn try_from(val: proto::apply_request::El) -> Result<Self, Self::Error> {
291 use proto::apply_request::El;
292
293 match val {
294 El::Member(member) => member.try_into(),
295 El::Room(proto::Room { id, .. })
296 | El::WebrtcPub(proto::WebRtcPublishEndpoint { id, .. })
297 | El::WebrtcPlay(proto::WebRtcPlayEndpoint { id, .. }) => {
298 Err(ProtobufError::ExpectedElement("Member", id.into()))
299 }
300 }
301 }
302}
303
304impl TryFrom<proto::room::element::El> for Member {
305 type Error = ProtobufError;
306
307 fn try_from(val: proto::room::element::El) -> Result<Self, Self::Error> {
308 use proto::room::element::El;
309
310 match val {
311 El::Member(member) => member.try_into(),
312 El::WebrtcPub(proto::WebRtcPublishEndpoint { id, .. })
313 | El::WebrtcPlay(proto::WebRtcPlayEndpoint { id, .. }) => {
314 Err(ProtobufError::ExpectedElement("Member", id.into()))
315 }
316 }
317 }
318}
319
320impl TryFrom<proto::Member> for Member {
321 type Error = ProtobufError;
322
323 fn try_from(member: proto::Member) -> Result<Self, Self::Error> {
324 fn parse_duration<T: TryInto<Duration>>(
327 duration: Option<T>,
328 member_id: &str,
329 field: &'static str,
330 ) -> Result<Option<Duration>, ProtobufError> {
331 #[expect(clippy::map_err_ignore, reason = "not useful")]
332 duration.map(TryInto::try_into).transpose().map_err(|_| {
333 ProtobufError::InvalidDuration(member_id.into(), field)
334 })
335 }
336
337 let idle_timeout =
338 parse_duration(member.idle_timeout, &member.id, "idle_timeout")?;
339 let reconnect_timeout = parse_duration(
340 member.reconnect_timeout,
341 &member.id,
342 "reconnect_timeout",
343 )?;
344 let ping_interval =
345 parse_duration(member.ping_interval, &member.id, "ping_interval")?;
346
347 Ok(Self {
348 id: member.id.into(),
349 spec: member::Spec {
350 pipeline: member
351 .pipeline
352 .into_iter()
353 .map(|(id, member_el)| {
354 member_el.el.map_or_else(
355 || Err(ProtobufError::NoElementForId(id.into())),
356 |el| Endpoint::try_from(el).map(|e| (e.id, e.spec)),
357 )
358 })
359 .collect::<Result<_, _>>()?,
360 credentials: member.credentials.map(Into::into),
361 on_join: (!member.on_join.is_empty())
362 .then(|| member.on_join.parse())
363 .transpose()
364 .map_err(CallbackUrlParseError::from)?,
365 on_leave: (!member.on_leave.is_empty())
366 .then(|| member.on_leave.parse())
367 .transpose()
368 .map_err(CallbackUrlParseError::from)?,
369 idle_timeout,
370 reconnect_timeout,
371 ping_interval,
372 },
373 })
374 }
375}
376
377impl From<Member> for proto::Member {
378 fn from(member: Member) -> Self {
379 #[expect( clippy::unwrap_used,
381 reason = " `Duration` values are not expected to be large"
382 )]
383 Self {
384 id: member.id.into(),
385 on_join: member
386 .spec
387 .on_join
388 .as_ref()
389 .map_or_else(String::default, ToString::to_string),
390 on_leave: member
391 .spec
392 .on_leave
393 .as_ref()
394 .map_or_else(String::default, ToString::to_string),
395 idle_timeout: member
396 .spec
397 .idle_timeout
398 .map(|d| d.try_into().unwrap()),
399 reconnect_timeout: member
400 .spec
401 .reconnect_timeout
402 .map(|d| d.try_into().unwrap()),
403 ping_interval: member
404 .spec
405 .ping_interval
406 .map(|d| d.try_into().unwrap()),
407 pipeline: member
408 .spec
409 .pipeline
410 .into_iter()
411 .map(|(id, spec)| {
412 (id.clone().into(), Endpoint { id, spec }.into())
413 })
414 .collect(),
415 credentials: member.spec.credentials.map(Into::into),
416 }
417 }
418}
419
420impl From<Member> for proto::create_request::El {
421 fn from(member: Member) -> Self {
422 Self::Member(member.into())
423 }
424}
425
426impl From<Member> for proto::apply_request::El {
427 fn from(member: Member) -> Self {
428 Self::Member(member.into())
429 }
430}
431
432impl From<Member> for proto::room::Element {
433 fn from(member: Member) -> Self {
434 Self { el: Some(proto::room::element::El::Member(member.into())) }
435 }
436}
437
438impl From<proto::member::Credentials> for Credentials {
439 fn from(val: proto::member::Credentials) -> Self {
440 use proto::member::Credentials as Creds;
441
442 match val {
443 Creds::Hash(hash) => Self::Hash(hash.into()),
444 Creds::Plain(plain) => Self::Plain(plain.into()),
445 }
446 }
447}
448
449impl From<Credentials> for proto::member::Credentials {
450 fn from(creds: Credentials) -> Self {
451 match creds {
452 Credentials::Hash(hash) => Self::Hash(hash.into()),
453 Credentials::Plain(plain) => Self::Plain(plain.expose_str().into()),
454 }
455 }
456}
457
458impl TryFrom<proto::create_request::El> for Endpoint {
459 type Error = ProtobufError;
460
461 fn try_from(val: proto::create_request::El) -> Result<Self, Self::Error> {
462 use proto::create_request::El;
463
464 match val {
465 El::WebrtcPlay(play) => WebRtcPlay::try_from(play).map(Self::from),
466 El::WebrtcPub(publish) => {
467 WebRtcPublish::try_from(publish).map(Self::from)
468 }
469 El::Room(proto::Room { id, .. })
470 | El::Member(proto::Member { id, .. }) => {
471 Err(ProtobufError::ExpectedElement("Endpoint", id.into()))
472 }
473 }
474 }
475}
476
477impl TryFrom<proto::member::element::El> for Endpoint {
478 type Error = ProtobufError;
479
480 fn try_from(val: proto::member::element::El) -> Result<Self, Self::Error> {
481 use proto::member::element::El;
482
483 match val {
484 El::WebrtcPub(e) => WebRtcPublish::try_from(e).map(Self::from),
485 El::WebrtcPlay(e) => WebRtcPlay::try_from(e).map(Self::from),
486 }
487 }
488}
489
490impl From<Endpoint> for proto::member::Element {
491 fn from(endpoint: Endpoint) -> Self {
492 use proto::member::element::El;
493
494 Self {
495 el: Some(match endpoint.spec {
496 endpoint::Spec::WebRtcPublishEndpoint(spec) => El::WebrtcPub(
497 WebRtcPublish {
498 id: String::from(endpoint.id).into(),
499 spec,
500 }
501 .into(),
502 ),
503 endpoint::Spec::WebRtcPlayEndpoint(spec) => El::WebrtcPlay(
504 WebRtcPlay { id: String::from(endpoint.id).into(), spec }
505 .into(),
506 ),
507 }),
508 }
509 }
510}
511
512impl From<Endpoint> for proto::element::El {
513 fn from(endpoint: Endpoint) -> Self {
514 match endpoint.spec {
515 endpoint::Spec::WebRtcPublishEndpoint(spec) => Self::WebrtcPub(
516 WebRtcPublish { id: String::from(endpoint.id).into(), spec }
517 .into(),
518 ),
519 endpoint::Spec::WebRtcPlayEndpoint(spec) => Self::WebrtcPlay(
520 WebRtcPlay { id: String::from(endpoint.id).into(), spec }
521 .into(),
522 ),
523 }
524 }
525}
526
527impl From<Endpoint> for proto::create_request::El {
528 fn from(endpoint: Endpoint) -> Self {
529 match endpoint.spec {
530 endpoint::Spec::WebRtcPublishEndpoint(spec) => Self::WebrtcPub(
531 WebRtcPublish { id: String::from(endpoint.id).into(), spec }
532 .into(),
533 ),
534 endpoint::Spec::WebRtcPlayEndpoint(spec) => Self::WebrtcPlay(
535 WebRtcPlay { id: String::from(endpoint.id).into(), spec }
536 .into(),
537 ),
538 }
539 }
540}
541
542impl From<Endpoint> for proto::apply_request::El {
543 fn from(endpoint: Endpoint) -> Self {
544 match endpoint.spec {
545 endpoint::Spec::WebRtcPublishEndpoint(spec) => Self::WebrtcPub(
546 WebRtcPublish { id: String::from(endpoint.id).into(), spec }
547 .into(),
548 ),
549 endpoint::Spec::WebRtcPlayEndpoint(spec) => Self::WebrtcPlay(
550 WebRtcPlay { id: String::from(endpoint.id).into(), spec }
551 .into(),
552 ),
553 }
554 }
555}
556
557impl TryFrom<proto::WebRtcPlayEndpoint> for WebRtcPlay {
558 type Error = ProtobufError;
559
560 fn try_from(val: proto::WebRtcPlayEndpoint) -> Result<Self, Self::Error> {
561 Ok(Self {
562 id: val.id.into(),
563 spec: web_rtc_play::Spec {
564 src: val.src.parse()?,
565 force_relay: val.force_relay,
566 },
567 })
568 }
569}
570
571impl From<WebRtcPlay> for proto::WebRtcPlayEndpoint {
572 fn from(play: WebRtcPlay) -> Self {
573 Self {
574 id: play.id.into(),
575 src: play.spec.src.to_string(),
576 on_start: String::new(),
577 on_stop: String::new(),
578 force_relay: play.spec.force_relay,
579 }
580 }
581}
582
583impl TryFrom<proto::WebRtcPublishEndpoint> for WebRtcPublish {
584 type Error = ProtobufError;
585
586 fn try_from(
587 val: proto::WebRtcPublishEndpoint,
588 ) -> Result<Self, Self::Error> {
589 use proto::web_rtc_publish_endpoint::P2p;
590
591 Ok(Self {
592 id: val.id.into(),
593 spec: web_rtc_publish::Spec {
594 p2p: P2p::try_from(val.p2p).unwrap_or_default().into(),
595 audio_settings: val
596 .audio_settings
597 .map(AudioSettings::from)
598 .unwrap_or_default(),
599 video_settings: val
600 .video_settings
601 .map(VideoSettings::from)
602 .unwrap_or_default(),
603 force_relay: val.force_relay,
604 },
605 })
606 }
607}
608
609impl From<WebRtcPublish> for proto::WebRtcPublishEndpoint {
610 fn from(publish: WebRtcPublish) -> Self {
611 use proto::web_rtc_publish_endpoint::P2p;
612
613 Self {
614 id: publish.id.into(),
615 p2p: P2p::from(publish.spec.p2p).into(),
616 on_start: String::new(),
617 on_stop: String::new(),
618 force_relay: publish.spec.force_relay,
619 audio_settings: Some(publish.spec.audio_settings.into()),
620 video_settings: Some(publish.spec.video_settings.into()),
621 }
622 }
623}
624
625impl From<proto::web_rtc_publish_endpoint::AudioSettings> for AudioSettings {
626 fn from(val: proto::web_rtc_publish_endpoint::AudioSettings) -> Self {
627 use proto::web_rtc_publish_endpoint::PublishPolicy;
628
629 Self {
630 publish_policy: PublishPolicy::try_from(val.publish_policy)
631 .unwrap_or_default()
632 .into(),
633 }
634 }
635}
636
637impl From<AudioSettings> for proto::web_rtc_publish_endpoint::AudioSettings {
638 fn from(settings: AudioSettings) -> Self {
639 use proto::web_rtc_publish_endpoint::PublishPolicy;
640
641 Self {
642 publish_policy: PublishPolicy::from(settings.publish_policy).into(),
643 }
644 }
645}
646
647impl From<proto::web_rtc_publish_endpoint::VideoSettings> for VideoSettings {
648 fn from(val: proto::web_rtc_publish_endpoint::VideoSettings) -> Self {
649 use proto::web_rtc_publish_endpoint::PublishPolicy;
650
651 Self {
652 publish_policy: PublishPolicy::try_from(val.publish_policy)
653 .unwrap_or_default()
654 .into(),
655 }
656 }
657}
658
659impl From<VideoSettings> for proto::web_rtc_publish_endpoint::VideoSettings {
660 fn from(settings: VideoSettings) -> Self {
661 use proto::web_rtc_publish_endpoint::PublishPolicy;
662
663 Self {
664 publish_policy: PublishPolicy::from(settings.publish_policy).into(),
665 }
666 }
667}
668
669impl From<proto::web_rtc_publish_endpoint::PublishPolicy> for Policy {
670 fn from(val: proto::web_rtc_publish_endpoint::PublishPolicy) -> Self {
671 use proto::web_rtc_publish_endpoint::PublishPolicy as Proto;
672
673 match val {
674 Proto::Optional => Self::Optional,
675 Proto::Required => Self::Required,
676 Proto::Disabled => Self::Disabled,
677 }
678 }
679}
680
681impl From<Policy> for proto::web_rtc_publish_endpoint::PublishPolicy {
682 fn from(val: Policy) -> Self {
683 match val {
684 Policy::Optional => Self::Optional,
685 Policy::Required => Self::Required,
686 Policy::Disabled => Self::Disabled,
687 }
688 }
689}
690
691impl From<proto::web_rtc_publish_endpoint::P2p> for P2pMode {
692 fn from(val: proto::web_rtc_publish_endpoint::P2p) -> Self {
693 use proto::web_rtc_publish_endpoint::P2p;
694
695 match val {
696 P2p::Always => Self::Always,
697 P2p::IfPossible => Self::IfPossible,
698 P2p::Never => Self::Never,
699 }
700 }
701}
702
703impl From<P2pMode> for proto::web_rtc_publish_endpoint::P2p {
704 fn from(val: P2pMode) -> Self {
705 match val {
706 P2pMode::Always => Self::Always,
707 P2pMode::IfPossible => Self::IfPossible,
708 P2pMode::Never => Self::Never,
709 }
710 }
711}
712
713impl From<proto::Ping> for Ping {
714 fn from(val: proto::Ping) -> Self {
715 Self(val.nonce)
716 }
717}
718
719impl From<Ping> for proto::Ping {
720 fn from(val: Ping) -> Self {
721 Self { nonce: val.0 }
722 }
723}
724
725impl From<proto::Pong> for Pong {
726 fn from(val: proto::Pong) -> Self {
727 Self(val.nonce)
728 }
729}
730
731impl From<Pong> for proto::Pong {
732 fn from(val: Pong) -> Self {
733 Self { nonce: val.0 }
734 }
735}