1macro_rules! impl_dial_string_with_variables {
13 ($ty:ty) => {
14 impl $crate::commands::endpoint::DialString for $ty {
15 fn variables(&self) -> Option<&$crate::commands::variables::Variables> {
16 self.variables
17 .as_ref()
18 }
19 fn variables_mut(&mut self) -> Option<&mut $crate::commands::variables::Variables> {
20 self.variables
21 .as_mut()
22 }
23 fn set_variables(&mut self, vars: Option<$crate::commands::variables::Variables>) {
24 self.variables = vars;
25 }
26 }
27
28 impl $ty {
29 pub fn with_variables(
31 mut self,
32 variables: $crate::commands::variables::Variables,
33 ) -> Self {
34 self.variables = Some(variables);
35 self
36 }
37 }
38 };
39 ($ty:ty, |$this:ident, $f:ident| $body:expr) => {
40 impl_dial_string_with_variables!($ty);
41
42 impl $ty {
43 pub(super) fn write_for(
44 &self,
45 $f: &mut ::std::fmt::Formatter<'_>,
46 carrier: $crate::commands::variables::DialStringCarrier,
47 ) -> ::std::fmt::Result {
48 $crate::commands::endpoint::write_variables($f, &self.variables, carrier)?;
49 let $this = self;
50 $body
51 }
52 }
53
54 impl ::std::fmt::Display for $ty {
55 fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
56 self.write_for(f, $crate::commands::variables::DialStringCarrier::EslApi)
57 }
58 }
59 };
60}
61
62macro_rules! forward_to_variant {
65 ($self:ident, $method:ident $(, $arg:expr)?) => {
66 match $self {
67 Self::Sofia(ep) => ep.$method($($arg)?),
68 Self::SofiaGateway(ep) => ep.$method($($arg)?),
69 Self::Loopback(ep) => ep.$method($($arg)?),
70 Self::User(ep) => ep.$method($($arg)?),
71 Self::SofiaContact(ep) => ep.$method($($arg)?),
72 Self::GroupCall(ep) => ep.$method($($arg)?),
73 Self::Error(ep) => ep.$method($($arg)?),
74 Self::PortAudio(ep) | Self::PulseAudio(ep) | Self::Alsa(ep) => ep.$method($($arg)?),
75 }
76 };
77}
78
79mod audio;
80mod error;
81mod group_call;
82mod loopback;
83mod sofia;
84mod user;
85
86pub use audio::AudioEndpoint;
87pub use error::ErrorEndpoint;
88pub use group_call::{GroupCall, GroupCallOrder, ParseGroupCallOrderError};
89pub use loopback::LoopbackEndpoint;
90pub use sofia::{SofiaContact, SofiaEndpoint, SofiaGateway};
91pub use user::UserEndpoint;
92
93use std::fmt;
94use std::str::FromStr;
95
96use super::find_matching_bracket;
97use super::originate::OriginateError;
98use super::variables::{DialStringCarrier, Variables, VariablesType};
99
100pub trait DialString: fmt::Display {
105 fn variables(&self) -> Option<&Variables>;
107 fn variables_mut(&mut self) -> Option<&mut Variables>;
109 fn set_variables(&mut self, vars: Option<Variables>);
111}
112
113fn write_variables(
118 f: &mut fmt::Formatter<'_>,
119 vars: &Option<Variables>,
120 carrier: DialStringCarrier,
121) -> fmt::Result {
122 if let Some(vars) = vars {
123 if !vars.is_empty() {
124 write!(f, "{}", vars.display_for(carrier))?;
125 }
126 }
127 Ok(())
128}
129
130pub(super) fn strip_endpoint_prefix<'a>(
134 s: &'a str,
135 prefix: &str,
136 kind: &str,
137 carrier: DialStringCarrier,
138) -> Result<(Option<Variables>, &'a str), OriginateError> {
139 let (variables, uri) = extract_variables(s, carrier)?;
140 let rest = uri
141 .strip_prefix(prefix)
142 .filter(|rest| prefix.ends_with('/') || rest.is_empty() || rest.starts_with('/'))
143 .ok_or_else(|| OriginateError::ParseError(format!("not a {} endpoint", kind)))?;
144 Ok((variables, rest))
145}
146
147const ANY_SCOPE: &[VariablesType] = &[
149 VariablesType::Default,
150 VariablesType::Enterprise,
151 VariablesType::Channel,
152];
153
154fn extract_variables(
157 s: &str,
158 carrier: DialStringCarrier,
159) -> Result<(Option<Variables>, &str), OriginateError> {
160 extract_scoped_variables(s, carrier, ANY_SCOPE)
161}
162
163pub(super) fn extract_scoped_variables<'a>(
169 s: &'a str,
170 carrier: DialStringCarrier,
171 scopes: &[VariablesType],
172) -> Result<(Option<Variables>, &'a str), OriginateError> {
173 let first = s
174 .as_bytes()
175 .first()
176 .copied();
177 let Some((open, close_ch)) = scopes
178 .iter()
179 .map(|scope| scope.delimiters())
180 .find(|(open, _)| first == Some(*open as u8))
181 else {
182 return Ok((None, s));
183 };
184 let close = find_matching_bracket(s, open, close_ch)
185 .ok_or_else(|| OriginateError::ParseError(format!("unclosed {} in dial string", open)))?;
186 let var_str = &s[..=close];
187 let vars = Variables::parse_for(var_str, carrier)?;
188 let vars = if vars.is_empty() { None } else { Some(vars) };
189 Ok((vars, s[close + 1..].trim()))
190}
191
192#[derive(Debug, Clone, PartialEq, Eq)]
201#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
202#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
203#[non_exhaustive]
204pub enum Endpoint {
205 Sofia(SofiaEndpoint),
207 SofiaGateway(SofiaGateway),
209 Loopback(LoopbackEndpoint),
211 User(UserEndpoint),
213 SofiaContact(SofiaContact),
215 GroupCall(GroupCall),
217 Error(ErrorEndpoint),
219 #[cfg_attr(feature = "serde", serde(rename = "portaudio"))]
221 PortAudio(AudioEndpoint),
222 #[cfg_attr(feature = "serde", serde(rename = "pulseaudio"))]
224 PulseAudio(AudioEndpoint),
225 Alsa(AudioEndpoint),
227}
228
229impl From<SofiaEndpoint> for Endpoint {
234 fn from(ep: SofiaEndpoint) -> Self {
235 Self::Sofia(ep)
236 }
237}
238
239impl From<SofiaGateway> for Endpoint {
240 fn from(ep: SofiaGateway) -> Self {
241 Self::SofiaGateway(ep)
242 }
243}
244
245impl From<LoopbackEndpoint> for Endpoint {
246 fn from(ep: LoopbackEndpoint) -> Self {
247 Self::Loopback(ep)
248 }
249}
250
251impl From<UserEndpoint> for Endpoint {
252 fn from(ep: UserEndpoint) -> Self {
253 Self::User(ep)
254 }
255}
256
257impl From<SofiaContact> for Endpoint {
258 fn from(ep: SofiaContact) -> Self {
259 Self::SofiaContact(ep)
260 }
261}
262
263impl From<GroupCall> for Endpoint {
264 fn from(ep: GroupCall) -> Self {
265 Self::GroupCall(ep)
266 }
267}
268
269impl From<ErrorEndpoint> for Endpoint {
270 fn from(ep: ErrorEndpoint) -> Self {
271 Self::Error(ep)
272 }
273}
274
275impl Endpoint {
280 pub(crate) fn write_for(
281 &self,
282 f: &mut fmt::Formatter<'_>,
283 carrier: DialStringCarrier,
284 ) -> fmt::Result {
285 match self {
286 Self::Sofia(ep) => ep.write_for(f, carrier),
287 Self::SofiaGateway(ep) => ep.write_for(f, carrier),
288 Self::Loopback(ep) => ep.write_for(f, carrier),
289 Self::User(ep) => ep.write_for(f, carrier),
290 Self::SofiaContact(ep) => ep.write_for(f, carrier),
291 Self::GroupCall(ep) => ep.write_for(f, carrier),
292 Self::Error(ep) => fmt::Display::fmt(ep, f),
293 Self::PortAudio(ep) => ep.write_with_prefix(f, "portaudio", carrier),
294 Self::PulseAudio(ep) => ep.write_with_prefix(f, "pulseaudio", carrier),
295 Self::Alsa(ep) => ep.write_with_prefix(f, "alsa", carrier),
296 }
297 }
298
299 pub fn display_for(&self, carrier: DialStringCarrier) -> EndpointDisplay<'_> {
302 EndpointDisplay {
303 endpoint: self,
304 carrier,
305 }
306 }
307
308 pub fn parse_for(s: &str, carrier: DialStringCarrier) -> Result<Self, OriginateError> {
312 let (variables, rest) = extract_variables(s, carrier)?;
316 let mut endpoint = Self::parse_bare(rest)?;
317 if variables.is_some() {
318 endpoint.set_variables(variables);
319 if endpoint
320 .variables()
321 .is_none()
322 {
323 return Err(OriginateError::VariablesNotSupported(endpoint.kind()));
324 }
325 }
326 Ok(endpoint)
327 }
328
329 fn kind(&self) -> &'static str {
331 match self {
332 Self::Sofia(_) => "sofia",
333 Self::SofiaGateway(_) => "sofia gateway",
334 Self::Loopback(_) => "loopback",
335 Self::User(_) => "user",
336 Self::SofiaContact(_) => "sofia_contact",
337 Self::GroupCall(_) => "group_call",
338 Self::Error(_) => "error",
339 Self::PortAudio(_) => "portaudio",
340 Self::PulseAudio(_) => "pulseaudio",
341 Self::Alsa(_) => "alsa",
342 }
343 }
344
345 fn parse_bare(uri: &str) -> Result<Self, OriginateError> {
348 if uri.starts_with("${sofia_contact(") {
349 Ok(Self::SofiaContact(uri.parse()?))
350 } else if uri.starts_with("${group_call(") {
351 Ok(Self::GroupCall(uri.parse()?))
352 } else if uri.starts_with("error/") {
353 Ok(Self::Error(uri.parse()?))
354 } else if uri.starts_with("loopback/") {
355 Ok(Self::Loopback(uri.parse()?))
356 } else if uri.starts_with("sofia/gateway/") {
357 Ok(Self::SofiaGateway(uri.parse()?))
358 } else if uri.starts_with("sofia/") {
359 Ok(Self::Sofia(uri.parse()?))
360 } else if uri.starts_with("user/") {
361 Ok(Self::User(uri.parse()?))
362 } else if uri.starts_with("portaudio") {
363 Ok(Self::PortAudio(AudioEndpoint::parse_with_prefix(
364 uri,
365 "portaudio",
366 )?))
367 } else if uri.starts_with("pulseaudio") {
368 Ok(Self::PulseAudio(AudioEndpoint::parse_with_prefix(
369 uri,
370 "pulseaudio",
371 )?))
372 } else if uri.starts_with("alsa") {
373 Ok(Self::Alsa(AudioEndpoint::parse_with_prefix(uri, "alsa")?))
374 } else {
375 Err(OriginateError::UnknownEndpointType(uri.to_string()))
376 }
377 }
378}
379
380#[derive(Debug, Clone, Copy)]
383pub struct EndpointDisplay<'a> {
384 endpoint: &'a Endpoint,
385 carrier: DialStringCarrier,
386}
387
388impl fmt::Display for EndpointDisplay<'_> {
389 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
390 self.endpoint
391 .write_for(f, self.carrier)
392 }
393}
394
395impl fmt::Display for Endpoint {
396 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
397 self.write_for(f, DialStringCarrier::EslApi)
398 }
399}
400
401impl FromStr for Endpoint {
406 type Err = OriginateError;
407
408 fn from_str(s: &str) -> Result<Self, Self::Err> {
409 Self::parse_for(s, DialStringCarrier::EslApi)
410 }
411}
412
413impl DialString for ErrorEndpoint {
418 fn variables(&self) -> Option<&Variables> {
419 None
420 }
421 fn variables_mut(&mut self) -> Option<&mut Variables> {
422 None
423 }
424 fn set_variables(&mut self, _vars: Option<Variables>) {}
425}
426
427impl DialString for Endpoint {
428 fn variables(&self) -> Option<&Variables> {
429 forward_to_variant!(self, variables)
430 }
431 fn variables_mut(&mut self) -> Option<&mut Variables> {
432 forward_to_variant!(self, variables_mut)
433 }
434 fn set_variables(&mut self, vars: Option<Variables>) {
435 forward_to_variant!(self, set_variables, vars)
436 }
437}
438
439#[cfg(test)]
444mod tests {
445 use super::*;
446 use crate::commands::variables::VariablesType;
447
448 #[test]
451 fn extract_variables_nested_angle_brackets() {
452 let (vars, rest) = extract_variables(
453 "<sip_h_Call-Info=<url>>sofia/gw/x",
454 DialStringCarrier::EslApi,
455 )
456 .unwrap();
457 assert_eq!(rest, "sofia/gw/x");
458 assert!(vars.is_some());
459 }
460
461 #[test]
462 fn extract_variables_nested_curly_brackets() {
463 let (vars, rest) =
464 extract_variables("{a={b}}sofia/internal/1000", DialStringCarrier::EslApi).unwrap();
465 assert_eq!(rest, "sofia/internal/1000");
466 assert!(vars.is_some());
467 }
468
469 #[test]
470 fn extract_variables_unclosed_returns_error() {
471 let result = extract_variables("{a=b", DialStringCarrier::EslApi);
472 assert!(result.is_err());
473 }
474
475 #[test]
478 fn endpoint_from_str_sofia() {
479 let ep: Endpoint = "sofia/internal/1000@example.com"
480 .parse()
481 .unwrap();
482 assert!(matches!(ep, Endpoint::Sofia(_)));
483 }
484
485 #[test]
486 fn endpoint_from_str_sofia_gateway() {
487 let ep: Endpoint = "sofia/gateway/my_gw/1234"
488 .parse()
489 .unwrap();
490 assert!(matches!(ep, Endpoint::SofiaGateway(_)));
491 }
492
493 #[test]
494 fn endpoint_from_str_loopback() {
495 let ep: Endpoint = "loopback/9199/test"
496 .parse()
497 .unwrap();
498 assert!(matches!(ep, Endpoint::Loopback(_)));
499 }
500
501 #[test]
502 fn endpoint_from_str_user() {
503 let ep: Endpoint = "user/1000@example.com"
504 .parse()
505 .unwrap();
506 assert!(matches!(ep, Endpoint::User(_)));
507 }
508
509 #[test]
510 fn endpoint_from_str_sofia_contact() {
511 let ep: Endpoint = "${sofia_contact(1000@example.com)}"
512 .parse()
513 .unwrap();
514 assert!(matches!(ep, Endpoint::SofiaContact(_)));
515 }
516
517 #[test]
518 fn endpoint_from_str_group_call() {
519 let ep: Endpoint = "${group_call(support@example.com+A)}"
520 .parse()
521 .unwrap();
522 assert!(matches!(ep, Endpoint::GroupCall(_)));
523 }
524
525 #[test]
526 fn endpoint_from_str_error() {
527 let ep: Endpoint = "error/USER_BUSY"
528 .parse()
529 .unwrap();
530 assert!(matches!(ep, Endpoint::Error(_)));
531 }
532
533 #[test]
536 fn a_block_on_an_endpoint_that_cannot_hold_one_is_refused() {
537 for carrier in [DialStringCarrier::EslApi, DialStringCarrier::Dialplan] {
538 let msg = Endpoint::parse_for("{a=b}error/USER_BUSY", carrier)
539 .expect_err(&format!("accepted at {carrier:?}"))
540 .to_string();
541 assert!(msg.contains("error"), "does not name the type: {msg}");
542 }
543 assert!("{a=b}error/USER_BUSY"
544 .parse::<Endpoint>()
545 .is_err());
546 }
547
548 #[test]
551 fn from_str_matches_parse_for_at_the_default_carrier() {
552 for input in [
553 "sofia/internal/1000@example.com",
554 "{a=b}sofia/internal/1000@example.com",
555 "<a=b>loopback/9199/default",
556 "[a=b]user/bob@example.com",
557 "{a=b}error/USER_BUSY",
558 "verto/1234",
559 ] {
560 assert_eq!(
561 input
562 .parse::<Endpoint>()
563 .is_ok(),
564 Endpoint::parse_for(input, DialStringCarrier::EslApi).is_ok(),
565 "{input}"
566 );
567 }
568 }
569
570 #[test]
571 fn endpoint_from_str_unknown_errors() {
572 let result = "verto/1234".parse::<Endpoint>();
573 assert!(result.is_err());
574 }
575
576 #[test]
577 fn endpoint_from_str_with_variables() {
578 let ep: Endpoint = "{timeout=30}sofia/internal/1000@example.com"
579 .parse()
580 .unwrap();
581 if let Endpoint::Sofia(inner) = &ep {
582 assert_eq!(inner.profile, "internal");
583 assert!(inner
584 .variables
585 .is_some());
586 } else {
587 panic!("expected Sofia variant");
588 }
589 }
590
591 #[test]
594 fn endpoint_display_delegates_to_inner() {
595 let ep = Endpoint::Sofia(SofiaEndpoint {
596 profile: "internal".into(),
597 destination: "1000@example.com".into(),
598 variables: None,
599 });
600 assert_eq!(ep.to_string(), "sofia/internal/1000@example.com");
601 }
602
603 #[test]
606 fn dial_string_variables_returns_some() {
607 let mut vars = Variables::new(VariablesType::Default);
608 vars.insert("k", "v");
609 let ep = SofiaEndpoint {
610 profile: "internal".into(),
611 destination: "1000".into(),
612 variables: Some(vars),
613 };
614 assert!(ep
615 .variables()
616 .is_some());
617 assert_eq!(
618 ep.variables()
619 .unwrap()
620 .get("k"),
621 Some("v")
622 );
623 }
624
625 #[test]
626 fn dial_string_variables_returns_none() {
627 let ep = SofiaEndpoint {
628 profile: "internal".into(),
629 destination: "1000".into(),
630 variables: None,
631 };
632 assert!(ep
633 .variables()
634 .is_none());
635 }
636
637 #[test]
638 fn dial_string_set_variables() {
639 let mut ep = SofiaEndpoint {
640 profile: "internal".into(),
641 destination: "1000".into(),
642 variables: None,
643 };
644 let mut vars = Variables::new(VariablesType::Channel);
645 vars.insert("k", "v");
646 ep.set_variables(Some(vars));
647 assert!(ep
648 .variables()
649 .is_some());
650 }
651
652 #[test]
653 fn dial_string_error_endpoint_no_variables() {
654 let ep = ErrorEndpoint::new(crate::channel::HangupCause::UserBusy);
655 assert!(ep
656 .variables()
657 .is_none());
658 }
659
660 #[test]
661 fn dial_string_on_endpoint_enum() {
662 let mut vars = Variables::new(VariablesType::Default);
663 vars.insert("k", "v");
664 let ep = Endpoint::Sofia(SofiaEndpoint {
665 profile: "internal".into(),
666 destination: "1000".into(),
667 variables: Some(vars),
668 });
669 assert!(ep
670 .variables()
671 .is_some());
672 }
673
674 #[test]
679 fn serde_endpoint_enum_tags_round_trip() {
680 let cases: [(&str, Endpoint); 7] = [
681 (
682 "sofia",
683 SofiaEndpoint::new("internal", "1000@example.com").into(),
684 ),
685 ("sofia_gateway", SofiaGateway::new("gw1", "1234").into()),
686 (
687 "loopback",
688 LoopbackEndpoint::new("9199")
689 .with_context("default")
690 .into(),
691 ),
692 (
693 "user",
694 UserEndpoint::new("bob")
695 .with_domain("example.com")
696 .into(),
697 ),
698 (
699 "sofia_contact",
700 SofiaContact::new("1000", "example.com").into(),
701 ),
702 (
703 "group_call",
704 GroupCall::new("support", "example.com")
705 .with_order(GroupCallOrder::All)
706 .into(),
707 ),
708 (
709 "error",
710 ErrorEndpoint::new(crate::channel::HangupCause::UserBusy).into(),
711 ),
712 ];
713 for (tag, ep) in cases {
714 let json = serde_json::to_string(&ep).unwrap();
715 assert!(json.contains(&format!("\"{tag}\"")), "{tag}: {json}");
716 assert_eq!(serde_json::from_str::<Endpoint>(&json).unwrap(), ep);
717 }
718 }
719
720 #[test]
721 fn serde_endpoint_skips_none_variables() {
722 let ep = SofiaEndpoint {
723 profile: "internal".into(),
724 destination: "1000".into(),
725 variables: None,
726 };
727 let json = serde_json::to_string(&ep).unwrap();
728 assert!(!json.contains("variables"));
729 }
730
731 #[test]
732 fn serde_endpoint_skips_none_profile() {
733 let ep = SofiaGateway {
734 gateway: "gw".into(),
735 destination: "1234".into(),
736 profile: None,
737 variables: None,
738 };
739 let json = serde_json::to_string(&ep).unwrap();
740 assert!(!json.contains("profile"));
741 }
742
743 #[test]
748 fn audio_endpoints_render_and_parse_per_module() {
749 type Variant = fn(AudioEndpoint) -> Endpoint;
750 let cases: [(Variant, &str, &str); 6] = [
751 (Endpoint::PortAudio, "portaudio", "portaudio/auto_answer"),
752 (Endpoint::PortAudio, "portaudio", "portaudio"),
753 (Endpoint::PulseAudio, "pulseaudio", "pulseaudio/auto_answer"),
754 (Endpoint::PulseAudio, "pulseaudio", "pulseaudio"),
755 (Endpoint::Alsa, "alsa", "alsa/auto_answer"),
756 (Endpoint::Alsa, "alsa", "alsa"),
757 ];
758 for (variant, module, wire) in cases {
759 let destination = wire
760 .strip_prefix(module)
761 .and_then(|rest| rest.strip_prefix('/'))
762 .map(str::to_string);
763 let ep = variant(AudioEndpoint {
764 destination: destination.clone(),
765 variables: None,
766 });
767 assert_eq!(ep.to_string(), wire);
768
769 let parsed: Endpoint = wire
770 .parse()
771 .unwrap();
772 assert_eq!(parsed, ep, "{wire}");
773 assert!(
774 parsed
775 .variables()
776 .is_none(),
777 "{wire}"
778 );
779
780 let json = serde_json::to_string(&ep).unwrap();
781 assert!(json.contains(&format!("\"{module}\"")), "{wire}: {json}");
782 assert_eq!(serde_json::from_str::<Endpoint>(&json).unwrap(), ep);
783 }
784 }
785
786 #[test]
787 fn audio_endpoint_carries_variables() {
788 let mut vars = Variables::new(VariablesType::Default);
789 vars.insert("codec", "PCMU");
790 let ep = Endpoint::PortAudio(
791 AudioEndpoint::new()
792 .with_destination("auto_answer")
793 .with_variables(vars),
794 );
795 assert_eq!(ep.to_string(), "{codec=PCMU}portaudio/auto_answer");
796 assert_eq!(
797 ep.to_string()
798 .parse::<Endpoint>()
799 .unwrap(),
800 ep
801 );
802 }
803
804 #[test]
808 fn every_endpoint_type_round_trips_at_the_dialplan_carrier() {
809 let mut vars = Variables::new(VariablesType::Channel);
810 vars.insert("cid", "it's");
811 vars.insert("other", "don't");
812
813 let with_vars = |mut ep: Endpoint| {
814 ep.set_variables(Some(vars.clone()));
815 ep
816 };
817 let cases: [Endpoint; 10] = [
818 with_vars(SofiaEndpoint::new("internal", "1000@example.com").into()),
819 with_vars(
820 SofiaGateway::new("gw", "1234")
821 .with_profile("external")
822 .into(),
823 ),
824 with_vars(
825 LoopbackEndpoint::new("9199")
826 .with_context("default")
827 .into(),
828 ),
829 with_vars(
830 UserEndpoint::new("bob")
831 .with_domain("example.com")
832 .into(),
833 ),
834 with_vars(
835 SofiaContact::new("1000", "example.com")
836 .with_profile("*")
837 .into(),
838 ),
839 with_vars(
840 GroupCall::new("support", "example.com")
841 .with_order(GroupCallOrder::All)
842 .into(),
843 ),
844 ErrorEndpoint::new(crate::channel::HangupCause::UserBusy).into(),
845 with_vars(Endpoint::PortAudio(
846 AudioEndpoint::new().with_destination("auto_answer"),
847 )),
848 with_vars(Endpoint::PulseAudio(AudioEndpoint::new())),
849 with_vars(Endpoint::Alsa(
850 AudioEndpoint::new().with_destination("auto_answer"),
851 )),
852 ];
853
854 for ep in cases {
855 let rendered = ep
856 .display_for(DialStringCarrier::Dialplan)
857 .to_string();
858 let back = Endpoint::parse_for(&rendered, DialStringCarrier::Dialplan)
859 .unwrap_or_else(|e| panic!("{rendered} failed to parse: {e}"));
860 assert_eq!(back, ep, "rendered {rendered}");
861 }
862 }
863
864 #[test]
867 fn from_sofia_endpoint() {
868 let inner = SofiaEndpoint {
869 profile: "internal".into(),
870 destination: "1000@example.com".into(),
871 variables: None,
872 };
873 let ep: Endpoint = inner
874 .clone()
875 .into();
876 assert_eq!(ep, Endpoint::Sofia(inner));
877 }
878
879 #[test]
880 fn from_sofia_gateway() {
881 let inner = SofiaGateway {
882 gateway: "gw1".into(),
883 destination: "1234".into(),
884 profile: None,
885 variables: None,
886 };
887 let ep: Endpoint = inner
888 .clone()
889 .into();
890 assert_eq!(ep, Endpoint::SofiaGateway(inner));
891 }
892
893 #[test]
894 fn from_loopback_endpoint() {
895 let inner = LoopbackEndpoint::new("9199").with_context("default");
896 let ep: Endpoint = inner
897 .clone()
898 .into();
899 assert_eq!(ep, Endpoint::Loopback(inner));
900 }
901
902 #[test]
903 fn from_user_endpoint() {
904 let inner = UserEndpoint {
905 name: "bob".into(),
906 domain: Some("example.com".into()),
907 variables: None,
908 };
909 let ep: Endpoint = inner
910 .clone()
911 .into();
912 assert_eq!(ep, Endpoint::User(inner));
913 }
914
915 #[test]
916 fn from_sofia_contact() {
917 let inner = SofiaContact {
918 user: "1000".into(),
919 domain: "example.com".into(),
920 profile: None,
921 variables: None,
922 };
923 let ep: Endpoint = inner
924 .clone()
925 .into();
926 assert_eq!(ep, Endpoint::SofiaContact(inner));
927 }
928
929 #[test]
930 fn from_group_call() {
931 let inner = GroupCall::new("support", "example.com").with_order(GroupCallOrder::All);
932 let ep: Endpoint = inner
933 .clone()
934 .into();
935 assert_eq!(ep, Endpoint::GroupCall(inner));
936 }
937
938 #[test]
939 fn from_error_endpoint() {
940 let inner = ErrorEndpoint::new(crate::channel::HangupCause::UserBusy);
941 let ep: Endpoint = inner.into();
942 assert_eq!(ep, Endpoint::Error(inner));
943 }
944}