1mod audio;
9mod error;
10mod group_call;
11mod loopback;
12mod sofia;
13mod user;
14
15pub use audio::AudioEndpoint;
16pub use error::ErrorEndpoint;
17pub use group_call::{GroupCall, GroupCallOrder, ParseGroupCallOrderError};
18pub use loopback::LoopbackEndpoint;
19pub use sofia::{SofiaContact, SofiaEndpoint, SofiaGateway};
20pub use user::UserEndpoint;
21
22use std::fmt;
23use std::str::FromStr;
24
25use serde::{Deserialize, Serialize};
26
27use super::find_matching_bracket;
28use super::originate::{OriginateError, Variables};
29
30pub trait DialString: fmt::Display {
35 fn variables(&self) -> Option<&Variables>;
37 fn variables_mut(&mut self) -> Option<&mut Variables>;
39 fn set_variables(&mut self, vars: Option<Variables>);
41}
42
43fn write_variables(f: &mut fmt::Formatter<'_>, vars: &Option<Variables>) -> fmt::Result {
48 if let Some(vars) = vars {
49 if !vars.is_empty() {
50 write!(f, "{}", vars)?;
51 }
52 }
53 Ok(())
54}
55
56fn extract_variables(s: &str) -> Result<(Option<Variables>, &str), OriginateError> {
62 let (open, close_ch) = match s
63 .as_bytes()
64 .first()
65 {
66 Some(b'{') => ('{', '}'),
67 Some(b'[') => ('[', ']'),
68 Some(b'<') => ('<', '>'),
69 _ => return Ok((None, s)),
70 };
71 let close = find_matching_bracket(s, open, close_ch)
72 .ok_or_else(|| OriginateError::ParseError(format!("unclosed {} in endpoint", open)))?;
73 let var_str = &s[..=close];
74 let vars: Variables = var_str.parse()?;
75 let vars = if vars.is_empty() { None } else { Some(vars) };
76 Ok((vars, s[close + 1..].trim()))
77}
78
79#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
88#[serde(rename_all = "snake_case")]
89#[non_exhaustive]
90pub enum Endpoint {
91 Sofia(SofiaEndpoint),
93 SofiaGateway(SofiaGateway),
95 Loopback(LoopbackEndpoint),
97 User(UserEndpoint),
99 SofiaContact(SofiaContact),
101 GroupCall(GroupCall),
103 Error(ErrorEndpoint),
105 #[serde(rename = "portaudio")]
107 PortAudio(AudioEndpoint),
108 #[serde(rename = "pulseaudio")]
110 PulseAudio(AudioEndpoint),
111 Alsa(AudioEndpoint),
113}
114
115impl From<SofiaEndpoint> for Endpoint {
120 fn from(ep: SofiaEndpoint) -> Self {
121 Self::Sofia(ep)
122 }
123}
124
125impl From<SofiaGateway> for Endpoint {
126 fn from(ep: SofiaGateway) -> Self {
127 Self::SofiaGateway(ep)
128 }
129}
130
131impl From<LoopbackEndpoint> for Endpoint {
132 fn from(ep: LoopbackEndpoint) -> Self {
133 Self::Loopback(ep)
134 }
135}
136
137impl From<UserEndpoint> for Endpoint {
138 fn from(ep: UserEndpoint) -> Self {
139 Self::User(ep)
140 }
141}
142
143impl From<SofiaContact> for Endpoint {
144 fn from(ep: SofiaContact) -> Self {
145 Self::SofiaContact(ep)
146 }
147}
148
149impl From<GroupCall> for Endpoint {
150 fn from(ep: GroupCall) -> Self {
151 Self::GroupCall(ep)
152 }
153}
154
155impl From<ErrorEndpoint> for Endpoint {
156 fn from(ep: ErrorEndpoint) -> Self {
157 Self::Error(ep)
158 }
159}
160
161impl fmt::Display for Endpoint {
166 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
167 match self {
168 Self::Sofia(ep) => ep.fmt(f),
169 Self::SofiaGateway(ep) => ep.fmt(f),
170 Self::Loopback(ep) => ep.fmt(f),
171 Self::User(ep) => ep.fmt(f),
172 Self::SofiaContact(ep) => ep.fmt(f),
173 Self::GroupCall(ep) => ep.fmt(f),
174 Self::Error(ep) => ep.fmt(f),
175 Self::PortAudio(ep) => ep.fmt_with_prefix(f, "portaudio"),
176 Self::PulseAudio(ep) => ep.fmt_with_prefix(f, "pulseaudio"),
177 Self::Alsa(ep) => ep.fmt_with_prefix(f, "alsa"),
178 }
179 }
180}
181
182impl FromStr for Endpoint {
187 type Err = OriginateError;
188
189 fn from_str(s: &str) -> Result<Self, Self::Err> {
190 let (variables, uri) = extract_variables(s)?;
191 let full = if variables.is_some() {
193 s.to_string()
194 } else {
195 uri.to_string()
196 };
197
198 if uri.starts_with("${sofia_contact(") {
199 Ok(Self::SofiaContact(full.parse()?))
200 } else if uri.starts_with("${group_call(") {
201 Ok(Self::GroupCall(full.parse()?))
202 } else if uri.starts_with("error/") {
203 Ok(Self::Error(full.parse()?))
204 } else if uri.starts_with("loopback/") {
205 Ok(Self::Loopback(full.parse()?))
206 } else if uri.starts_with("sofia/gateway/") {
207 Ok(Self::SofiaGateway(full.parse()?))
208 } else if uri.starts_with("sofia/") {
209 Ok(Self::Sofia(full.parse()?))
210 } else if uri.starts_with("user/") {
211 Ok(Self::User(full.parse()?))
212 } else if uri.starts_with("portaudio") {
213 Ok(Self::PortAudio(AudioEndpoint::parse_with_prefix(
214 &full,
215 "portaudio",
216 )?))
217 } else if uri.starts_with("pulseaudio") {
218 Ok(Self::PulseAudio(AudioEndpoint::parse_with_prefix(
219 &full,
220 "pulseaudio",
221 )?))
222 } else if uri.starts_with("alsa") {
223 Ok(Self::Alsa(AudioEndpoint::parse_with_prefix(&full, "alsa")?))
224 } else {
225 Err(OriginateError::ParseError(format!(
226 "unknown endpoint type: {}",
227 uri
228 )))
229 }
230 }
231}
232
233macro_rules! impl_dial_string_with_variables {
238 ($ty:ty) => {
239 impl DialString for $ty {
240 fn variables(&self) -> Option<&Variables> {
241 self.variables
242 .as_ref()
243 }
244 fn variables_mut(&mut self) -> Option<&mut Variables> {
245 self.variables
246 .as_mut()
247 }
248 fn set_variables(&mut self, vars: Option<Variables>) {
249 self.variables = vars;
250 }
251 }
252 };
253}
254
255impl_dial_string_with_variables!(SofiaEndpoint);
256impl_dial_string_with_variables!(SofiaGateway);
257impl_dial_string_with_variables!(LoopbackEndpoint);
258impl_dial_string_with_variables!(UserEndpoint);
259impl_dial_string_with_variables!(SofiaContact);
260impl_dial_string_with_variables!(GroupCall);
261impl_dial_string_with_variables!(AudioEndpoint);
262
263impl DialString for ErrorEndpoint {
264 fn variables(&self) -> Option<&Variables> {
265 None
266 }
267 fn variables_mut(&mut self) -> Option<&mut Variables> {
268 None
269 }
270 fn set_variables(&mut self, _vars: Option<Variables>) {}
271}
272
273impl DialString for Endpoint {
274 fn variables(&self) -> Option<&Variables> {
275 match self {
276 Self::Sofia(ep) => ep.variables(),
277 Self::SofiaGateway(ep) => ep.variables(),
278 Self::Loopback(ep) => ep.variables(),
279 Self::User(ep) => ep.variables(),
280 Self::SofiaContact(ep) => ep.variables(),
281 Self::GroupCall(ep) => ep.variables(),
282 Self::Error(ep) => ep.variables(),
283 Self::PortAudio(ep) | Self::PulseAudio(ep) | Self::Alsa(ep) => ep.variables(),
284 }
285 }
286 fn variables_mut(&mut self) -> Option<&mut Variables> {
287 match self {
288 Self::Sofia(ep) => ep.variables_mut(),
289 Self::SofiaGateway(ep) => ep.variables_mut(),
290 Self::Loopback(ep) => ep.variables_mut(),
291 Self::User(ep) => ep.variables_mut(),
292 Self::SofiaContact(ep) => ep.variables_mut(),
293 Self::GroupCall(ep) => ep.variables_mut(),
294 Self::Error(ep) => ep.variables_mut(),
295 Self::PortAudio(ep) | Self::PulseAudio(ep) | Self::Alsa(ep) => ep.variables_mut(),
296 }
297 }
298 fn set_variables(&mut self, vars: Option<Variables>) {
299 match self {
300 Self::Sofia(ep) => ep.set_variables(vars),
301 Self::SofiaGateway(ep) => ep.set_variables(vars),
302 Self::Loopback(ep) => ep.set_variables(vars),
303 Self::User(ep) => ep.set_variables(vars),
304 Self::SofiaContact(ep) => ep.set_variables(vars),
305 Self::GroupCall(ep) => ep.set_variables(vars),
306 Self::Error(ep) => ep.set_variables(vars),
307 Self::PortAudio(ep) | Self::PulseAudio(ep) | Self::Alsa(ep) => ep.set_variables(vars),
308 }
309 }
310}
311
312#[cfg(test)]
317mod tests {
318 use super::*;
319 use crate::commands::originate::VariablesType;
320
321 #[test]
324 fn extract_variables_nested_angle_brackets() {
325 let (vars, rest) = extract_variables("<sip_h_Call-Info=<url>>sofia/gw/x").unwrap();
326 assert_eq!(rest, "sofia/gw/x");
327 assert!(vars.is_some());
328 }
329
330 #[test]
331 fn extract_variables_nested_curly_brackets() {
332 let (vars, rest) = extract_variables("{a={b}}sofia/internal/1000").unwrap();
333 assert_eq!(rest, "sofia/internal/1000");
334 assert!(vars.is_some());
335 }
336
337 #[test]
338 fn extract_variables_unclosed_returns_error() {
339 let result = extract_variables("{a=b");
340 assert!(result.is_err());
341 }
342
343 #[test]
346 fn endpoint_from_str_sofia() {
347 let ep: Endpoint = "sofia/internal/1000@domain.com"
348 .parse()
349 .unwrap();
350 assert!(matches!(ep, Endpoint::Sofia(_)));
351 }
352
353 #[test]
354 fn endpoint_from_str_sofia_gateway() {
355 let ep: Endpoint = "sofia/gateway/my_gw/1234"
356 .parse()
357 .unwrap();
358 assert!(matches!(ep, Endpoint::SofiaGateway(_)));
359 }
360
361 #[test]
362 fn endpoint_from_str_loopback() {
363 let ep: Endpoint = "loopback/9199/test"
364 .parse()
365 .unwrap();
366 assert!(matches!(ep, Endpoint::Loopback(_)));
367 }
368
369 #[test]
370 fn endpoint_from_str_user() {
371 let ep: Endpoint = "user/1000@domain.com"
372 .parse()
373 .unwrap();
374 assert!(matches!(ep, Endpoint::User(_)));
375 }
376
377 #[test]
378 fn endpoint_from_str_sofia_contact() {
379 let ep: Endpoint = "${sofia_contact(1000@domain.com)}"
380 .parse()
381 .unwrap();
382 assert!(matches!(ep, Endpoint::SofiaContact(_)));
383 }
384
385 #[test]
386 fn endpoint_from_str_group_call() {
387 let ep: Endpoint = "${group_call(support@domain.com+A)}"
388 .parse()
389 .unwrap();
390 assert!(matches!(ep, Endpoint::GroupCall(_)));
391 }
392
393 #[test]
394 fn endpoint_from_str_error() {
395 let ep: Endpoint = "error/USER_BUSY"
396 .parse()
397 .unwrap();
398 assert!(matches!(ep, Endpoint::Error(_)));
399 assert!("error/user_busy"
400 .parse::<Endpoint>()
401 .is_err());
402 }
403
404 #[test]
405 fn endpoint_from_str_unknown_errors() {
406 let result = "verto/1234".parse::<Endpoint>();
407 assert!(result.is_err());
408 }
409
410 #[test]
411 fn endpoint_from_str_with_variables() {
412 let ep: Endpoint = "{timeout=30}sofia/internal/1000@domain.com"
413 .parse()
414 .unwrap();
415 if let Endpoint::Sofia(inner) = &ep {
416 assert_eq!(inner.profile, "internal");
417 assert!(inner
418 .variables
419 .is_some());
420 } else {
421 panic!("expected Sofia variant");
422 }
423 }
424
425 #[test]
428 fn endpoint_display_delegates_to_inner() {
429 let ep = Endpoint::Sofia(SofiaEndpoint {
430 profile: "internal".into(),
431 destination: "1000@domain.com".into(),
432 variables: None,
433 });
434 assert_eq!(ep.to_string(), "sofia/internal/1000@domain.com");
435 }
436
437 #[test]
440 fn dial_string_variables_returns_some() {
441 let mut vars = Variables::new(VariablesType::Default);
442 vars.insert("k", "v");
443 let ep = SofiaEndpoint {
444 profile: "internal".into(),
445 destination: "1000".into(),
446 variables: Some(vars),
447 };
448 assert!(ep
449 .variables()
450 .is_some());
451 assert_eq!(
452 ep.variables()
453 .unwrap()
454 .get("k"),
455 Some("v")
456 );
457 }
458
459 #[test]
460 fn dial_string_variables_returns_none() {
461 let ep = SofiaEndpoint {
462 profile: "internal".into(),
463 destination: "1000".into(),
464 variables: None,
465 };
466 assert!(ep
467 .variables()
468 .is_none());
469 }
470
471 #[test]
472 fn dial_string_set_variables() {
473 let mut ep = SofiaEndpoint {
474 profile: "internal".into(),
475 destination: "1000".into(),
476 variables: None,
477 };
478 let mut vars = Variables::new(VariablesType::Channel);
479 vars.insert("k", "v");
480 ep.set_variables(Some(vars));
481 assert!(ep
482 .variables()
483 .is_some());
484 }
485
486 #[test]
487 fn dial_string_error_endpoint_no_variables() {
488 let ep = ErrorEndpoint::new(crate::channel::HangupCause::UserBusy);
489 assert!(ep
490 .variables()
491 .is_none());
492 }
493
494 #[test]
495 fn dial_string_on_endpoint_enum() {
496 let mut vars = Variables::new(VariablesType::Default);
497 vars.insert("k", "v");
498 let ep = Endpoint::Sofia(SofiaEndpoint {
499 profile: "internal".into(),
500 destination: "1000".into(),
501 variables: Some(vars),
502 });
503 assert!(ep
504 .variables()
505 .is_some());
506 }
507
508 #[test]
511 fn serde_endpoint_enum_sofia() {
512 let ep = Endpoint::Sofia(SofiaEndpoint {
513 profile: "internal".into(),
514 destination: "1000@domain.com".into(),
515 variables: None,
516 });
517 let json = serde_json::to_string(&ep).unwrap();
518 assert!(json.contains("\"sofia\""));
519 let parsed: Endpoint = serde_json::from_str(&json).unwrap();
520 assert_eq!(parsed, ep);
521 }
522
523 #[test]
524 fn serde_endpoint_enum_sofia_gateway() {
525 let ep = Endpoint::SofiaGateway(SofiaGateway {
526 gateway: "gw1".into(),
527 destination: "1234".into(),
528 profile: None,
529 variables: None,
530 });
531 let json = serde_json::to_string(&ep).unwrap();
532 assert!(json.contains("\"sofia_gateway\""));
533 let parsed: Endpoint = serde_json::from_str(&json).unwrap();
534 assert_eq!(parsed, ep);
535 }
536
537 #[test]
538 fn serde_endpoint_enum_loopback() {
539 let ep = Endpoint::Loopback(LoopbackEndpoint::new("9199").with_context("default"));
540 let json = serde_json::to_string(&ep).unwrap();
541 assert!(json.contains("\"loopback\""));
542 let parsed: Endpoint = serde_json::from_str(&json).unwrap();
543 assert_eq!(parsed, ep);
544 }
545
546 #[test]
547 fn serde_endpoint_enum_user() {
548 let ep = Endpoint::User(UserEndpoint {
549 name: "bob".into(),
550 domain: Some("example.com".into()),
551 variables: None,
552 });
553 let json = serde_json::to_string(&ep).unwrap();
554 assert!(json.contains("\"user\""));
555 let parsed: Endpoint = serde_json::from_str(&json).unwrap();
556 assert_eq!(parsed, ep);
557 }
558
559 #[test]
560 fn serde_endpoint_enum_sofia_contact() {
561 let ep = Endpoint::SofiaContact(SofiaContact {
562 user: "1000".into(),
563 domain: "domain.com".into(),
564 profile: None,
565 variables: None,
566 });
567 let json = serde_json::to_string(&ep).unwrap();
568 assert!(json.contains("\"sofia_contact\""));
569 let parsed: Endpoint = serde_json::from_str(&json).unwrap();
570 assert_eq!(parsed, ep);
571 }
572
573 #[test]
574 fn serde_endpoint_enum_group_call() {
575 let ep = Endpoint::GroupCall(GroupCall {
576 group: "support".into(),
577 domain: "domain.com".into(),
578 order: Some(GroupCallOrder::All),
579 variables: None,
580 });
581 let json = serde_json::to_string(&ep).unwrap();
582 assert!(json.contains("\"group_call\""));
583 let parsed: Endpoint = serde_json::from_str(&json).unwrap();
584 assert_eq!(parsed, ep);
585 }
586
587 #[test]
588 fn serde_endpoint_enum_error() {
589 let ep = Endpoint::Error(ErrorEndpoint::new(crate::channel::HangupCause::UserBusy));
590 let json = serde_json::to_string(&ep).unwrap();
591 assert!(json.contains("\"error\""));
592 let parsed: Endpoint = serde_json::from_str(&json).unwrap();
593 assert_eq!(parsed, ep);
594 }
595
596 #[test]
597 fn serde_endpoint_skips_none_variables() {
598 let ep = SofiaEndpoint {
599 profile: "internal".into(),
600 destination: "1000".into(),
601 variables: None,
602 };
603 let json = serde_json::to_string(&ep).unwrap();
604 assert!(!json.contains("variables"));
605 }
606
607 #[test]
608 fn serde_endpoint_skips_none_profile() {
609 let ep = SofiaGateway {
610 gateway: "gw".into(),
611 destination: "1234".into(),
612 profile: None,
613 variables: None,
614 };
615 let json = serde_json::to_string(&ep).unwrap();
616 assert!(!json.contains("profile"));
617 }
618
619 #[test]
622 fn portaudio_display() {
623 let ep = AudioEndpoint {
624 destination: Some("auto_answer".into()),
625 variables: None,
626 };
627 let endpoint = Endpoint::PortAudio(ep);
628 assert_eq!(endpoint.to_string(), "portaudio/auto_answer");
629 }
630
631 #[test]
632 fn portaudio_bare_display() {
633 let ep = AudioEndpoint {
634 destination: None,
635 variables: None,
636 };
637 let endpoint = Endpoint::PortAudio(ep);
638 assert_eq!(endpoint.to_string(), "portaudio");
639 }
640
641 #[test]
642 fn portaudio_from_str() {
643 let ep: Endpoint = "portaudio/auto_answer"
644 .parse()
645 .unwrap();
646 if let Endpoint::PortAudio(audio) = ep {
647 assert_eq!(
648 audio
649 .destination
650 .as_deref(),
651 Some("auto_answer")
652 );
653 } else {
654 panic!("expected PortAudio");
655 }
656 }
657
658 #[test]
659 fn portaudio_bare_from_str() {
660 let ep: Endpoint = "portaudio"
661 .parse()
662 .unwrap();
663 if let Endpoint::PortAudio(audio) = ep {
664 assert!(audio
665 .destination
666 .is_none());
667 } else {
668 panic!("expected PortAudio");
669 }
670 }
671
672 #[test]
673 fn portaudio_round_trip() {
674 let input = "portaudio/auto_answer";
675 let ep: Endpoint = input
676 .parse()
677 .unwrap();
678 assert_eq!(ep.to_string(), input);
679 }
680
681 #[test]
682 fn portaudio_bare_round_trip() {
683 let input = "portaudio";
684 let ep: Endpoint = input
685 .parse()
686 .unwrap();
687 assert_eq!(ep.to_string(), input);
688 }
689
690 #[test]
691 fn portaudio_with_variables() {
692 let mut vars = Variables::new(VariablesType::Default);
693 vars.insert("codec", "PCMU");
694 let ep = Endpoint::PortAudio(AudioEndpoint {
695 destination: Some("auto_answer".into()),
696 variables: Some(vars),
697 });
698 assert_eq!(ep.to_string(), "{codec=PCMU}portaudio/auto_answer");
699 let parsed: Endpoint = ep
700 .to_string()
701 .parse()
702 .unwrap();
703 assert_eq!(parsed, ep);
704 }
705
706 #[test]
707 fn pulseaudio_display() {
708 let ep = Endpoint::PulseAudio(AudioEndpoint {
709 destination: Some("auto_answer".into()),
710 variables: None,
711 });
712 assert_eq!(ep.to_string(), "pulseaudio/auto_answer");
713 }
714
715 #[test]
716 fn pulseaudio_from_str() {
717 let ep: Endpoint = "pulseaudio/auto_answer"
718 .parse()
719 .unwrap();
720 assert!(matches!(ep, Endpoint::PulseAudio(_)));
721 }
722
723 #[test]
724 fn pulseaudio_round_trip() {
725 let input = "pulseaudio/auto_answer";
726 let ep: Endpoint = input
727 .parse()
728 .unwrap();
729 assert_eq!(ep.to_string(), input);
730 }
731
732 #[test]
733 fn alsa_display() {
734 let ep = Endpoint::Alsa(AudioEndpoint {
735 destination: Some("auto_answer".into()),
736 variables: None,
737 });
738 assert_eq!(ep.to_string(), "alsa/auto_answer");
739 }
740
741 #[test]
742 fn alsa_from_str() {
743 let ep: Endpoint = "alsa/auto_answer"
744 .parse()
745 .unwrap();
746 assert!(matches!(ep, Endpoint::Alsa(_)));
747 }
748
749 #[test]
750 fn alsa_bare_round_trip() {
751 let input = "alsa";
752 let ep: Endpoint = input
753 .parse()
754 .unwrap();
755 assert_eq!(ep.to_string(), input);
756 }
757
758 #[test]
759 fn serde_portaudio() {
760 let ep = Endpoint::PortAudio(AudioEndpoint {
761 destination: Some("auto_answer".into()),
762 variables: None,
763 });
764 let json = serde_json::to_string(&ep).unwrap();
765 assert!(json.contains("\"portaudio\""));
766 let parsed: Endpoint = serde_json::from_str(&json).unwrap();
767 assert_eq!(parsed, ep);
768 }
769
770 #[test]
771 fn serde_pulseaudio() {
772 let ep = Endpoint::PulseAudio(AudioEndpoint {
773 destination: Some("auto_answer".into()),
774 variables: None,
775 });
776 let json = serde_json::to_string(&ep).unwrap();
777 assert!(json.contains("\"pulseaudio\""));
778 let parsed: Endpoint = serde_json::from_str(&json).unwrap();
779 assert_eq!(parsed, ep);
780 }
781
782 #[test]
783 fn serde_alsa() {
784 let ep = Endpoint::Alsa(AudioEndpoint {
785 destination: None,
786 variables: None,
787 });
788 let json = serde_json::to_string(&ep).unwrap();
789 assert!(json.contains("\"alsa\""));
790 let parsed: Endpoint = serde_json::from_str(&json).unwrap();
791 assert_eq!(parsed, ep);
792 }
793
794 #[test]
797 fn from_sofia_endpoint() {
798 let inner = SofiaEndpoint {
799 profile: "internal".into(),
800 destination: "1000@domain.com".into(),
801 variables: None,
802 };
803 let ep: Endpoint = inner
804 .clone()
805 .into();
806 assert_eq!(ep, Endpoint::Sofia(inner));
807 }
808
809 #[test]
810 fn from_sofia_gateway() {
811 let inner = SofiaGateway {
812 gateway: "gw1".into(),
813 destination: "1234".into(),
814 profile: None,
815 variables: None,
816 };
817 let ep: Endpoint = inner
818 .clone()
819 .into();
820 assert_eq!(ep, Endpoint::SofiaGateway(inner));
821 }
822
823 #[test]
824 fn from_loopback_endpoint() {
825 let inner = LoopbackEndpoint::new("9199").with_context("default");
826 let ep: Endpoint = inner
827 .clone()
828 .into();
829 assert_eq!(ep, Endpoint::Loopback(inner));
830 }
831
832 #[test]
833 fn from_user_endpoint() {
834 let inner = UserEndpoint {
835 name: "bob".into(),
836 domain: Some("example.com".into()),
837 variables: None,
838 };
839 let ep: Endpoint = inner
840 .clone()
841 .into();
842 assert_eq!(ep, Endpoint::User(inner));
843 }
844
845 #[test]
846 fn from_sofia_contact() {
847 let inner = SofiaContact {
848 user: "1000".into(),
849 domain: "domain.com".into(),
850 profile: None,
851 variables: None,
852 };
853 let ep: Endpoint = inner
854 .clone()
855 .into();
856 assert_eq!(ep, Endpoint::SofiaContact(inner));
857 }
858
859 #[test]
860 fn from_group_call() {
861 let inner = GroupCall::new("support", "domain.com").with_order(GroupCallOrder::All);
862 let ep: Endpoint = inner
863 .clone()
864 .into();
865 assert_eq!(ep, Endpoint::GroupCall(inner));
866 }
867
868 #[test]
869 fn from_error_endpoint() {
870 let inner = ErrorEndpoint::new(crate::channel::HangupCause::UserBusy);
871 let ep: Endpoint = inner.into();
872 assert_eq!(ep, Endpoint::Error(inner));
873 }
874}