freeswitch_types/commands/endpoint/
loopback.rs1use std::fmt;
2use std::str::FromStr;
3
4use super::{strip_endpoint_prefix, write_variables};
5use crate::commands::originate::OriginateError;
6use crate::commands::variables::Variables;
7
8#[derive(Debug, Clone, PartialEq, Eq)]
10#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
11#[non_exhaustive]
12pub struct LoopbackEndpoint {
13 pub extension: String,
15 #[cfg_attr(
18 feature = "serde",
19 serde(default, skip_serializing_if = "Option::is_none")
20 )]
21 pub context: Option<String>,
22 #[cfg_attr(
24 feature = "serde",
25 serde(default, skip_serializing_if = "Option::is_none")
26 )]
27 pub variables: Option<Variables>,
28}
29
30impl LoopbackEndpoint {
31 pub fn new(extension: impl Into<String>) -> Self {
33 Self {
34 extension: extension.into(),
35 context: None,
36 variables: None,
37 }
38 }
39
40 pub fn with_context(mut self, context: impl Into<String>) -> Self {
42 self.context = Some(context.into());
43 self
44 }
45
46 pub fn with_variables(mut self, variables: Variables) -> Self {
48 self.variables = Some(variables);
49 self
50 }
51}
52
53impl fmt::Display for LoopbackEndpoint {
54 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
55 write_variables(f, &self.variables)?;
56 match &self.context {
57 Some(ctx) => write!(f, "loopback/{}/{}", self.extension, ctx),
58 None => write!(f, "loopback/{}", self.extension),
59 }
60 }
61}
62
63impl FromStr for LoopbackEndpoint {
64 type Err = OriginateError;
65
66 fn from_str(s: &str) -> Result<Self, Self::Err> {
67 let (variables, rest) = strip_endpoint_prefix(s, "loopback/", "loopback")?;
68 let (extension, context) = match rest.split_once('/') {
69 Some((ext, ctx)) => (ext, Some(ctx.to_string())),
70 None => (rest, None),
71 };
72 Ok(Self {
73 extension: extension.into(),
74 context,
75 variables,
76 })
77 }
78}
79
80#[cfg(test)]
81mod tests {
82 use super::*;
83 use crate::commands::variables::VariablesType;
84
85 #[test]
86 fn loopback_display_no_context() {
87 let ep = LoopbackEndpoint::new("9199");
88 assert_eq!(ep.to_string(), "loopback/9199");
89 }
90
91 #[test]
92 fn loopback_display_with_context() {
93 let ep = LoopbackEndpoint::new("9199").with_context("default");
94 assert_eq!(ep.to_string(), "loopback/9199/default");
95 }
96
97 #[test]
98 fn loopback_display_with_variables() {
99 let mut vars = Variables::new(VariablesType::Default);
100 vars.insert("loopback_initial_codec", "L16@48000h");
101 let ep = LoopbackEndpoint::new("100")
102 .with_context("test")
103 .with_variables(vars);
104 assert_eq!(
105 ep.to_string(),
106 "{loopback_initial_codec=L16@48000h}loopback/100/test"
107 );
108 }
109
110 #[test]
111 fn loopback_from_str_with_context() {
112 let ep: LoopbackEndpoint = "loopback/9199/test"
113 .parse()
114 .unwrap();
115 assert_eq!(ep.extension, "9199");
116 assert_eq!(
117 ep.context
118 .as_deref(),
119 Some("test")
120 );
121 }
122
123 #[test]
124 fn loopback_from_str_no_context() {
125 let ep: LoopbackEndpoint = "loopback/9199"
126 .parse()
127 .unwrap();
128 assert_eq!(ep.extension, "9199");
129 assert!(ep
130 .context
131 .is_none());
132 }
133
134 #[test]
135 fn loopback_round_trip_with_context() {
136 let ep = LoopbackEndpoint::new("100").with_context("myctx");
137 let s = ep.to_string();
138 let parsed: LoopbackEndpoint = s
139 .parse()
140 .unwrap();
141 assert_eq!(parsed, ep);
142 }
143
144 #[test]
145 fn loopback_round_trip_no_context() {
146 let ep = LoopbackEndpoint::new("9199");
147 let s = ep.to_string();
148 let parsed: LoopbackEndpoint = s
149 .parse()
150 .unwrap();
151 assert_eq!(parsed, ep);
152 }
153
154 #[test]
159 fn loopback_display_parse_display_stable() {
160 let inputs = [
161 "loopback/9199",
162 "loopback/100/default",
163 "loopback/ext123/custom_ctx",
164 ];
165 for input in inputs {
166 let parsed: LoopbackEndpoint = input
167 .parse()
168 .unwrap();
169 let displayed = parsed.to_string();
170 assert_eq!(displayed, input, "round-trip failed for: {}", input);
171 let reparsed: LoopbackEndpoint = displayed
172 .parse()
173 .unwrap();
174 assert_eq!(reparsed, parsed);
175 }
176 }
177
178 #[test]
179 fn serde_loopback_endpoint() {
180 let ep = LoopbackEndpoint::new("9199").with_context("default");
181 let json = serde_json::to_string(&ep).unwrap();
182 let parsed: LoopbackEndpoint = serde_json::from_str(&json).unwrap();
183 assert_eq!(parsed, ep);
184 }
185}