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