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 target: $crate::commands::variables::DialStringTarget,
47 ) -> ::std::fmt::Result {
48 $crate::commands::endpoint::write_variables($f, &self.variables, target)?;
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(
57 f,
58 $crate::commands::variables::DialStringCarrier::EslApi.into(),
59 )
60 }
61 }
62 };
63}
64
65macro_rules! forward_to_variant {
68 ($self:ident, $method:ident $(, $arg:expr)?) => {
69 match $self {
70 Self::Sofia(ep) => ep.$method($($arg)?),
71 Self::SofiaGateway(ep) => ep.$method($($arg)?),
72 Self::Loopback(ep) => ep.$method($($arg)?),
73 Self::User(ep) => ep.$method($($arg)?),
74 Self::SofiaContact(ep) => ep.$method($($arg)?),
75 Self::GroupCall(ep) => ep.$method($($arg)?),
76 Self::Error(ep) => ep.$method($($arg)?),
77 Self::PortAudio(ep) | Self::PulseAudio(ep) | Self::Alsa(ep) => ep.$method($($arg)?),
78 }
79 };
80}
81
82mod audio;
83mod error;
84mod group_call;
85mod loopback;
86mod sofia;
87mod user;
88
89pub use audio::AudioEndpoint;
90pub use error::ErrorEndpoint;
91pub use group_call::{GroupCall, GroupCallOrder, ParseGroupCallOrderError};
92pub use loopback::LoopbackEndpoint;
93pub use sofia::{SofiaContact, SofiaEndpoint, SofiaGateway};
94pub use user::UserEndpoint;
95
96use std::fmt;
97use std::str::FromStr;
98
99use super::find_matching_bracket;
100use super::originate::OriginateError;
101use super::variables::{DialStringCarrier, DialStringTarget, Variables, VariablesType};
102
103type PrefixParser = fn(&str) -> Result<Endpoint, OriginateError>;
104
105pub trait DialString: fmt::Display {
110 fn variables(&self) -> Option<&Variables>;
112 fn variables_mut(&mut self) -> Option<&mut Variables>;
114 fn set_variables(&mut self, vars: Option<Variables>);
116}
117
118fn write_variables(
123 f: &mut fmt::Formatter<'_>,
124 vars: &Option<Variables>,
125 target: DialStringTarget,
126) -> fmt::Result {
127 if let Some(vars) = vars {
128 if !vars.is_empty() {
129 write!(f, "{}", vars.display_for(target))?;
130 }
131 }
132 Ok(())
133}
134
135pub(super) fn strip_endpoint_prefix<'a>(
139 s: &'a str,
140 prefix: &str,
141 kind: &str,
142 carrier: DialStringCarrier,
143) -> Result<(Option<Variables>, &'a str), OriginateError> {
144 let (variables, uri) = extract_variables(s, carrier.into())?;
145 let rest = uri
146 .strip_prefix(prefix)
147 .filter(|rest| prefix.ends_with('/') || rest.is_empty() || rest.starts_with('/'))
148 .ok_or_else(|| OriginateError::ParseError(format!("not a {} endpoint", kind)))?;
149 Ok((variables, rest))
150}
151
152const ANY_SCOPE: &[VariablesType] = &[
154 VariablesType::Default,
155 VariablesType::Enterprise,
156 VariablesType::Channel,
157];
158
159fn extract_variables(
162 s: &str,
163 target: DialStringTarget,
164) -> Result<(Option<Variables>, &str), OriginateError> {
165 extract_scoped_variables(s, target, ANY_SCOPE)
166}
167
168pub(super) fn extract_scoped_variables<'a>(
174 s: &'a str,
175 target: DialStringTarget,
176 scopes: &[VariablesType],
177) -> Result<(Option<Variables>, &'a str), OriginateError> {
178 let first = s
179 .as_bytes()
180 .first()
181 .copied();
182 let Some((open, close_ch)) = scopes
183 .iter()
184 .map(|scope| scope.delimiters())
185 .find(|(open, _)| first == Some(*open as u8))
186 else {
187 return Ok((None, s));
188 };
189 let close = find_matching_bracket(s, open, close_ch)
190 .ok_or_else(|| OriginateError::ParseError(format!("unclosed {} in dial string", open)))?;
191 let var_str = &s[..=close];
192 let vars = Variables::parse_for(var_str, target)?;
193 let vars = if vars.is_empty() { None } else { Some(vars) };
194 Ok((vars, s[close + 1..].trim()))
195}
196
197#[derive(Debug, Clone, PartialEq, Eq)]
206#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
207#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
208#[non_exhaustive]
209pub enum Endpoint {
210 Sofia(SofiaEndpoint),
212 SofiaGateway(SofiaGateway),
214 Loopback(LoopbackEndpoint),
216 User(UserEndpoint),
218 SofiaContact(SofiaContact),
220 GroupCall(GroupCall),
222 Error(ErrorEndpoint),
224 #[cfg_attr(feature = "serde", serde(rename = "portaudio"))]
226 PortAudio(AudioEndpoint),
227 #[cfg_attr(feature = "serde", serde(rename = "pulseaudio"))]
229 PulseAudio(AudioEndpoint),
230 Alsa(AudioEndpoint),
232}
233
234impl From<SofiaEndpoint> for Endpoint {
239 fn from(ep: SofiaEndpoint) -> Self {
240 Self::Sofia(ep)
241 }
242}
243
244impl From<SofiaGateway> for Endpoint {
245 fn from(ep: SofiaGateway) -> Self {
246 Self::SofiaGateway(ep)
247 }
248}
249
250impl From<LoopbackEndpoint> for Endpoint {
251 fn from(ep: LoopbackEndpoint) -> Self {
252 Self::Loopback(ep)
253 }
254}
255
256impl From<UserEndpoint> for Endpoint {
257 fn from(ep: UserEndpoint) -> Self {
258 Self::User(ep)
259 }
260}
261
262impl From<SofiaContact> for Endpoint {
263 fn from(ep: SofiaContact) -> Self {
264 Self::SofiaContact(ep)
265 }
266}
267
268impl From<GroupCall> for Endpoint {
269 fn from(ep: GroupCall) -> Self {
270 Self::GroupCall(ep)
271 }
272}
273
274impl From<ErrorEndpoint> for Endpoint {
275 fn from(ep: ErrorEndpoint) -> Self {
276 Self::Error(ep)
277 }
278}
279
280impl Endpoint {
285 pub(crate) fn write_for(
286 &self,
287 f: &mut fmt::Formatter<'_>,
288 target: DialStringTarget,
289 ) -> fmt::Result {
290 match self {
291 Self::Sofia(ep) => ep.write_for(f, target),
292 Self::SofiaGateway(ep) => ep.write_for(f, target),
293 Self::Loopback(ep) => ep.write_for(f, target),
294 Self::User(ep) => ep.write_for(f, target),
295 Self::SofiaContact(ep) => ep.write_for(f, target),
296 Self::GroupCall(ep) => ep.write_for(f, target),
297 Self::Error(ep) => fmt::Display::fmt(ep, f),
298 Self::PortAudio(ep) => ep.write_with_prefix(f, "portaudio", target),
299 Self::PulseAudio(ep) => ep.write_with_prefix(f, "pulseaudio", target),
300 Self::Alsa(ep) => ep.write_with_prefix(f, "alsa", target),
301 }
302 }
303
304 pub fn display_for(&self, target: impl Into<DialStringTarget>) -> EndpointDisplay<'_> {
307 EndpointDisplay {
308 endpoint: self,
309 target: target.into(),
310 }
311 }
312
313 pub fn parse_for(s: &str, target: impl Into<DialStringTarget>) -> Result<Self, OriginateError> {
317 let (variables, rest) = extract_variables(s, target.into())?;
321 let mut endpoint = Self::parse_bare(rest)?;
322 if variables.is_some() {
323 endpoint.set_variables(variables);
324 if endpoint
325 .variables()
326 .is_none()
327 {
328 return Err(OriginateError::VariablesNotSupported(endpoint.kind()));
329 }
330 }
331 Ok(endpoint)
332 }
333
334 fn kind(&self) -> &'static str {
336 match self {
337 Self::Sofia(_) => "sofia",
338 Self::SofiaGateway(_) => "sofia gateway",
339 Self::Loopback(_) => "loopback",
340 Self::User(_) => "user",
341 Self::SofiaContact(_) => "sofia_contact",
342 Self::GroupCall(_) => "group_call",
343 Self::Error(_) => "error",
344 Self::PortAudio(_) => "portaudio",
345 Self::PulseAudio(_) => "pulseaudio",
346 Self::Alsa(_) => "alsa",
347 }
348 }
349
350 const PREFIX_PARSERS: &[(&str, PrefixParser)] = &[
352 ("${sofia_contact(", |u| Ok(Self::SofiaContact(u.parse()?))),
353 ("${group_call(", |u| Ok(Self::GroupCall(u.parse()?))),
354 ("error/", |u| Ok(Self::Error(u.parse()?))),
355 ("loopback/", |u| Ok(Self::Loopback(u.parse()?))),
356 ("sofia/gateway/", |u| Ok(Self::SofiaGateway(u.parse()?))),
358 ("sofia/", |u| Ok(Self::Sofia(u.parse()?))),
359 ("user/", |u| Ok(Self::User(u.parse()?))),
360 ("portaudio", |u| {
361 Ok(Self::PortAudio(AudioEndpoint::parse_with_prefix(
362 u,
363 "portaudio",
364 )?))
365 }),
366 ("pulseaudio", |u| {
367 Ok(Self::PulseAudio(AudioEndpoint::parse_with_prefix(
368 u,
369 "pulseaudio",
370 )?))
371 }),
372 ("alsa", |u| {
373 Ok(Self::Alsa(AudioEndpoint::parse_with_prefix(u, "alsa")?))
374 }),
375 ];
376
377 pub(crate) fn parse_bare(uri: &str) -> Result<Self, OriginateError> {
380 let (_, parse) = Self::PREFIX_PARSERS
381 .iter()
382 .find(|(prefix, _)| uri.starts_with(prefix))
383 .ok_or_else(|| OriginateError::UnknownEndpointType(uri.to_string()))?;
384 parse(uri)
385 }
386}
387
388#[derive(Debug, Clone, Copy)]
391pub struct EndpointDisplay<'a> {
392 endpoint: &'a Endpoint,
393 target: DialStringTarget,
394}
395
396impl fmt::Display for EndpointDisplay<'_> {
397 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
398 self.endpoint
399 .write_for(f, self.target)
400 }
401}
402
403impl fmt::Display for Endpoint {
404 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
405 self.write_for(f, DialStringCarrier::EslApi.into())
406 }
407}
408
409impl FromStr for Endpoint {
414 type Err = OriginateError;
415
416 fn from_str(s: &str) -> Result<Self, Self::Err> {
417 Self::parse_for(s, DialStringCarrier::EslApi)
418 }
419}
420
421impl DialString for ErrorEndpoint {
426 fn variables(&self) -> Option<&Variables> {
427 None
428 }
429 fn variables_mut(&mut self) -> Option<&mut Variables> {
430 None
431 }
432 fn set_variables(&mut self, _vars: Option<Variables>) {}
433}
434
435impl DialString for Endpoint {
436 fn variables(&self) -> Option<&Variables> {
437 forward_to_variant!(self, variables)
438 }
439 fn variables_mut(&mut self) -> Option<&mut Variables> {
440 forward_to_variant!(self, variables_mut)
441 }
442 fn set_variables(&mut self, vars: Option<Variables>) {
443 forward_to_variant!(self, set_variables, vars)
444 }
445}
446
447#[cfg(test)]
452mod tests {
453 use super::*;
454 use crate::commands::variables::VariablesType;
455
456 #[test]
459 fn extract_variables_nested_angle_brackets() {
460 let (vars, rest) = extract_variables(
461 "<sip_h_Call-Info=<url>>sofia/gw/x",
462 DialStringCarrier::EslApi.into(),
463 )
464 .unwrap();
465 assert_eq!(rest, "sofia/gw/x");
466 assert!(vars.is_some());
467 }
468
469 #[test]
470 fn extract_variables_nested_curly_brackets() {
471 let (vars, rest) = extract_variables(
472 "{a={b}}sofia/internal/1000",
473 DialStringCarrier::EslApi.into(),
474 )
475 .unwrap();
476 assert_eq!(rest, "sofia/internal/1000");
477 assert!(vars.is_some());
478 }
479
480 #[test]
481 fn extract_variables_unclosed_returns_error() {
482 let result = extract_variables("{a=b", DialStringCarrier::EslApi.into());
483 assert!(result.is_err());
484 }
485
486 #[test]
489 fn endpoint_from_str_sofia() {
490 let ep: Endpoint = "sofia/internal/1000@example.com"
491 .parse()
492 .unwrap();
493 assert!(matches!(ep, Endpoint::Sofia(_)));
494 }
495
496 #[test]
497 fn endpoint_from_str_sofia_gateway() {
498 let ep: Endpoint = "sofia/gateway/my_gw/1234"
499 .parse()
500 .unwrap();
501 assert!(matches!(ep, Endpoint::SofiaGateway(_)));
502 }
503
504 #[test]
505 fn endpoint_from_str_loopback() {
506 let ep: Endpoint = "loopback/9199/test"
507 .parse()
508 .unwrap();
509 assert!(matches!(ep, Endpoint::Loopback(_)));
510 }
511
512 #[test]
513 fn endpoint_from_str_user() {
514 let ep: Endpoint = "user/1000@example.com"
515 .parse()
516 .unwrap();
517 assert!(matches!(ep, Endpoint::User(_)));
518 }
519
520 #[test]
521 fn endpoint_from_str_sofia_contact() {
522 let ep: Endpoint = "${sofia_contact(1000@example.com)}"
523 .parse()
524 .unwrap();
525 assert!(matches!(ep, Endpoint::SofiaContact(_)));
526 }
527
528 #[test]
529 fn endpoint_from_str_group_call() {
530 let ep: Endpoint = "${group_call(support@example.com+A)}"
531 .parse()
532 .unwrap();
533 assert!(matches!(ep, Endpoint::GroupCall(_)));
534 }
535
536 #[test]
537 fn endpoint_from_str_error() {
538 let ep: Endpoint = "error/USER_BUSY"
539 .parse()
540 .unwrap();
541 assert!(matches!(ep, Endpoint::Error(_)));
542 }
543
544 #[test]
547 fn a_block_on_an_endpoint_that_cannot_hold_one_is_refused() {
548 for carrier in [DialStringCarrier::EslApi, DialStringCarrier::Dialplan] {
549 let msg = Endpoint::parse_for("{a=b}error/USER_BUSY", carrier)
550 .expect_err(&format!("accepted at {carrier:?}"))
551 .to_string();
552 assert!(msg.contains("error"), "does not name the type: {msg}");
553 }
554 assert!("{a=b}error/USER_BUSY"
555 .parse::<Endpoint>()
556 .is_err());
557 }
558
559 #[test]
562 fn from_str_matches_parse_for_at_the_default_carrier() {
563 for input in [
564 "sofia/internal/1000@example.com",
565 "{a=b}sofia/internal/1000@example.com",
566 "<a=b>loopback/9199/default",
567 "[a=b]user/bob@example.com",
568 "{a=b}error/USER_BUSY",
569 "verto/1234",
570 ] {
571 assert_eq!(
572 input
573 .parse::<Endpoint>()
574 .is_ok(),
575 Endpoint::parse_for(input, DialStringCarrier::EslApi).is_ok(),
576 "{input}"
577 );
578 }
579 }
580
581 #[test]
584 fn a_target_and_its_bare_carrier_agree() {
585 use crate::commands::variables::{BlockParse, DialStringTarget};
586
587 let input = r"{a=it\\\\\\'s}sofia/internal/1000@example.com";
588 let target = DialStringTarget::new(DialStringCarrier::Dialplan)
589 .with_block_parse(BlockParse::PairSplitCleans);
590 let by_target = Endpoint::parse_for(input, target).unwrap();
591 let by_carrier = Endpoint::parse_for(input, DialStringCarrier::Dialplan).unwrap();
592 assert_eq!(by_target, by_carrier);
593 assert_eq!(
594 by_target
595 .display_for(target)
596 .to_string(),
597 input
598 );
599 }
600
601 #[test]
602 fn endpoint_from_str_unknown_errors() {
603 let result = "verto/1234".parse::<Endpoint>();
604 assert!(result.is_err());
605 }
606
607 #[test]
608 fn endpoint_from_str_with_variables() {
609 let ep: Endpoint = "{timeout=30}sofia/internal/1000@example.com"
610 .parse()
611 .unwrap();
612 if let Endpoint::Sofia(inner) = &ep {
613 assert_eq!(inner.profile, "internal");
614 assert!(inner
615 .variables
616 .is_some());
617 } else {
618 panic!("expected Sofia variant");
619 }
620 }
621
622 #[test]
625 fn endpoint_display_delegates_to_inner() {
626 let ep = Endpoint::Sofia(SofiaEndpoint {
627 profile: "internal".into(),
628 destination: "1000@example.com".into(),
629 variables: None,
630 });
631 assert_eq!(ep.to_string(), "sofia/internal/1000@example.com");
632 }
633
634 #[test]
637 fn dial_string_variables_returns_some() {
638 let mut vars = Variables::new(VariablesType::Default);
639 vars.insert("k", "v");
640 let ep = SofiaEndpoint {
641 profile: "internal".into(),
642 destination: "1000".into(),
643 variables: Some(vars),
644 };
645 assert!(ep
646 .variables()
647 .is_some());
648 assert_eq!(
649 ep.variables()
650 .unwrap()
651 .get("k"),
652 Some("v")
653 );
654 }
655
656 #[test]
657 fn dial_string_variables_returns_none() {
658 let ep = SofiaEndpoint {
659 profile: "internal".into(),
660 destination: "1000".into(),
661 variables: None,
662 };
663 assert!(ep
664 .variables()
665 .is_none());
666 }
667
668 #[test]
669 fn dial_string_set_variables() {
670 let mut ep = SofiaEndpoint {
671 profile: "internal".into(),
672 destination: "1000".into(),
673 variables: None,
674 };
675 let mut vars = Variables::new(VariablesType::Channel);
676 vars.insert("k", "v");
677 ep.set_variables(Some(vars));
678 assert!(ep
679 .variables()
680 .is_some());
681 }
682
683 #[test]
684 fn dial_string_error_endpoint_no_variables() {
685 let ep = ErrorEndpoint::new(crate::channel::HangupCause::UserBusy);
686 assert!(ep
687 .variables()
688 .is_none());
689 }
690
691 #[test]
692 fn dial_string_on_endpoint_enum() {
693 let mut vars = Variables::new(VariablesType::Default);
694 vars.insert("k", "v");
695 let ep = Endpoint::Sofia(SofiaEndpoint {
696 profile: "internal".into(),
697 destination: "1000".into(),
698 variables: Some(vars),
699 });
700 assert!(ep
701 .variables()
702 .is_some());
703 }
704
705 #[test]
710 fn serde_endpoint_enum_tags_round_trip() {
711 let cases: [(&str, Endpoint); 7] = [
712 (
713 "sofia",
714 SofiaEndpoint::new("internal", "1000@example.com").into(),
715 ),
716 ("sofia_gateway", SofiaGateway::new("gw1", "1234").into()),
717 (
718 "loopback",
719 LoopbackEndpoint::new("9199")
720 .with_context("default")
721 .into(),
722 ),
723 (
724 "user",
725 UserEndpoint::new("bob")
726 .with_domain("example.com")
727 .into(),
728 ),
729 (
730 "sofia_contact",
731 SofiaContact::new("1000", "example.com").into(),
732 ),
733 (
734 "group_call",
735 GroupCall::new("support", "example.com")
736 .with_order(GroupCallOrder::All)
737 .into(),
738 ),
739 (
740 "error",
741 ErrorEndpoint::new(crate::channel::HangupCause::UserBusy).into(),
742 ),
743 ];
744 for (tag, ep) in cases {
745 let json = serde_json::to_string(&ep).unwrap();
746 assert!(json.contains(&format!("\"{tag}\"")), "{tag}: {json}");
747 assert_eq!(serde_json::from_str::<Endpoint>(&json).unwrap(), ep);
748 }
749 }
750
751 #[test]
752 fn serde_endpoint_skips_none_variables() {
753 let ep = SofiaEndpoint {
754 profile: "internal".into(),
755 destination: "1000".into(),
756 variables: None,
757 };
758 let json = serde_json::to_string(&ep).unwrap();
759 assert!(!json.contains("variables"));
760 }
761
762 #[test]
763 fn serde_endpoint_skips_none_profile() {
764 let ep = SofiaGateway {
765 gateway: "gw".into(),
766 destination: "1234".into(),
767 profile: None,
768 variables: None,
769 };
770 let json = serde_json::to_string(&ep).unwrap();
771 assert!(!json.contains("profile"));
772 }
773
774 #[test]
779 fn audio_endpoints_render_and_parse_per_module() {
780 type Variant = fn(AudioEndpoint) -> Endpoint;
781 let cases: [(Variant, &str, &str); 6] = [
782 (Endpoint::PortAudio, "portaudio", "portaudio/auto_answer"),
783 (Endpoint::PortAudio, "portaudio", "portaudio"),
784 (Endpoint::PulseAudio, "pulseaudio", "pulseaudio/auto_answer"),
785 (Endpoint::PulseAudio, "pulseaudio", "pulseaudio"),
786 (Endpoint::Alsa, "alsa", "alsa/auto_answer"),
787 (Endpoint::Alsa, "alsa", "alsa"),
788 ];
789 for (variant, module, wire) in cases {
790 let destination = wire
791 .strip_prefix(module)
792 .and_then(|rest| rest.strip_prefix('/'))
793 .map(str::to_string);
794 let ep = variant(AudioEndpoint {
795 destination: destination.clone(),
796 variables: None,
797 });
798 assert_eq!(ep.to_string(), wire);
799
800 let parsed: Endpoint = wire
801 .parse()
802 .unwrap();
803 assert_eq!(parsed, ep, "{wire}");
804 assert!(
805 parsed
806 .variables()
807 .is_none(),
808 "{wire}"
809 );
810
811 let json = serde_json::to_string(&ep).unwrap();
812 assert!(json.contains(&format!("\"{module}\"")), "{wire}: {json}");
813 assert_eq!(serde_json::from_str::<Endpoint>(&json).unwrap(), ep);
814 }
815 }
816
817 #[test]
818 fn audio_endpoint_carries_variables() {
819 let mut vars = Variables::new(VariablesType::Default);
820 vars.insert("codec", "PCMU");
821 let ep = Endpoint::PortAudio(
822 AudioEndpoint::new()
823 .with_destination("auto_answer")
824 .with_variables(vars),
825 );
826 assert_eq!(ep.to_string(), "{codec=PCMU}portaudio/auto_answer");
827 assert_eq!(
828 ep.to_string()
829 .parse::<Endpoint>()
830 .unwrap(),
831 ep
832 );
833 }
834
835 #[test]
838 fn every_endpoint_type_round_trips_at_the_dialplan_carrier() {
839 let mut vars = Variables::new(VariablesType::Channel);
840 vars.insert("path", r"C:\path");
841 vars.insert("other", "a,b");
842
843 let with_vars = |mut ep: Endpoint| {
844 ep.set_variables(Some(vars.clone()));
845 ep
846 };
847 let cases: [Endpoint; 10] = [
848 with_vars(SofiaEndpoint::new("internal", "1000@example.com").into()),
849 with_vars(
850 SofiaGateway::new("gw", "1234")
851 .with_profile("external")
852 .into(),
853 ),
854 with_vars(
855 LoopbackEndpoint::new("9199")
856 .with_context("default")
857 .into(),
858 ),
859 with_vars(
860 UserEndpoint::new("bob")
861 .with_domain("example.com")
862 .into(),
863 ),
864 with_vars(
865 SofiaContact::new("1000", "example.com")
866 .with_profile("*")
867 .into(),
868 ),
869 with_vars(
870 GroupCall::new("support", "example.com")
871 .with_order(GroupCallOrder::All)
872 .into(),
873 ),
874 ErrorEndpoint::new(crate::channel::HangupCause::UserBusy).into(),
875 with_vars(Endpoint::PortAudio(
876 AudioEndpoint::new().with_destination("auto_answer"),
877 )),
878 with_vars(Endpoint::PulseAudio(AudioEndpoint::new())),
879 with_vars(Endpoint::Alsa(
880 AudioEndpoint::new().with_destination("auto_answer"),
881 )),
882 ];
883
884 for ep in cases {
885 let rendered = ep
886 .display_for(DialStringCarrier::Dialplan)
887 .to_string();
888 let back = Endpoint::parse_for(&rendered, DialStringCarrier::Dialplan)
889 .unwrap_or_else(|e| panic!("{rendered} failed to parse: {e}"));
890 assert_eq!(back, ep, "rendered {rendered}");
891 }
892 }
893
894 #[test]
897 fn from_sofia_endpoint() {
898 let inner = SofiaEndpoint {
899 profile: "internal".into(),
900 destination: "1000@example.com".into(),
901 variables: None,
902 };
903 let ep: Endpoint = inner
904 .clone()
905 .into();
906 assert_eq!(ep, Endpoint::Sofia(inner));
907 }
908
909 #[test]
910 fn from_sofia_gateway() {
911 let inner = SofiaGateway {
912 gateway: "gw1".into(),
913 destination: "1234".into(),
914 profile: None,
915 variables: None,
916 };
917 let ep: Endpoint = inner
918 .clone()
919 .into();
920 assert_eq!(ep, Endpoint::SofiaGateway(inner));
921 }
922
923 #[test]
924 fn from_loopback_endpoint() {
925 let inner = LoopbackEndpoint::new("9199").with_context("default");
926 let ep: Endpoint = inner
927 .clone()
928 .into();
929 assert_eq!(ep, Endpoint::Loopback(inner));
930 }
931
932 #[test]
933 fn from_user_endpoint() {
934 let inner = UserEndpoint {
935 name: "bob".into(),
936 domain: Some("example.com".into()),
937 variables: None,
938 };
939 let ep: Endpoint = inner
940 .clone()
941 .into();
942 assert_eq!(ep, Endpoint::User(inner));
943 }
944
945 #[test]
946 fn from_sofia_contact() {
947 let inner = SofiaContact {
948 user: "1000".into(),
949 domain: "example.com".into(),
950 profile: None,
951 variables: None,
952 };
953 let ep: Endpoint = inner
954 .clone()
955 .into();
956 assert_eq!(ep, Endpoint::SofiaContact(inner));
957 }
958
959 #[test]
960 fn from_group_call() {
961 let inner = GroupCall::new("support", "example.com").with_order(GroupCallOrder::All);
962 let ep: Endpoint = inner
963 .clone()
964 .into();
965 assert_eq!(ep, Endpoint::GroupCall(inner));
966 }
967
968 #[test]
969 fn from_error_endpoint() {
970 let inner = ErrorEndpoint::new(crate::channel::HangupCause::UserBusy);
971 let ep: Endpoint = inner.into();
972 assert_eq!(ep, Endpoint::Error(inner));
973 }
974}