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