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, 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, rest) = strip_endpoint_prefix(s, "loopback/", "loopback")?;
67 let (extension, context) = match rest.split_once('/') {
68 Some((ext, ctx)) => (ext, Some(ctx.to_string())),
69 None => (rest, None),
70 };
71 Ok(Self {
72 extension: extension.into(),
73 context,
74 variables,
75 })
76 }
77}
78
79#[cfg(test)]
80mod tests {
81 use super::*;
82 use crate::commands::originate::VariablesType;
83
84 #[test]
85 fn loopback_display_no_context() {
86 let ep = LoopbackEndpoint::new("9199");
87 assert_eq!(ep.to_string(), "loopback/9199");
88 }
89
90 #[test]
91 fn loopback_display_with_context() {
92 let ep = LoopbackEndpoint::new("9199").with_context("default");
93 assert_eq!(ep.to_string(), "loopback/9199/default");
94 }
95
96 #[test]
97 fn loopback_display_with_variables() {
98 let mut vars = Variables::new(VariablesType::Default);
99 vars.insert("loopback_initial_codec", "L16@48000h");
100 let ep = LoopbackEndpoint::new("100")
101 .with_context("test")
102 .with_variables(vars);
103 assert_eq!(
104 ep.to_string(),
105 "{loopback_initial_codec=L16@48000h}loopback/100/test"
106 );
107 }
108
109 #[test]
110 fn loopback_from_str_with_context() {
111 let ep: LoopbackEndpoint = "loopback/9199/test"
112 .parse()
113 .unwrap();
114 assert_eq!(ep.extension, "9199");
115 assert_eq!(
116 ep.context
117 .as_deref(),
118 Some("test")
119 );
120 }
121
122 #[test]
123 fn loopback_from_str_no_context() {
124 let ep: LoopbackEndpoint = "loopback/9199"
125 .parse()
126 .unwrap();
127 assert_eq!(ep.extension, "9199");
128 assert!(ep
129 .context
130 .is_none());
131 }
132
133 #[test]
134 fn loopback_round_trip_with_context() {
135 let ep = LoopbackEndpoint::new("100").with_context("myctx");
136 let s = ep.to_string();
137 let parsed: LoopbackEndpoint = s
138 .parse()
139 .unwrap();
140 assert_eq!(parsed, ep);
141 }
142
143 #[test]
144 fn loopback_round_trip_no_context() {
145 let ep = LoopbackEndpoint::new("9199");
146 let s = ep.to_string();
147 let parsed: LoopbackEndpoint = s
148 .parse()
149 .unwrap();
150 assert_eq!(parsed, ep);
151 }
152
153 #[test]
158 fn loopback_display_parse_display_stable() {
159 let inputs = [
160 "loopback/9199",
161 "loopback/100/default",
162 "loopback/ext123/custom_ctx",
163 ];
164 for input in inputs {
165 let parsed: LoopbackEndpoint = input
166 .parse()
167 .unwrap();
168 let displayed = parsed.to_string();
169 assert_eq!(displayed, input, "round-trip failed for: {}", input);
170 let reparsed: LoopbackEndpoint = displayed
171 .parse()
172 .unwrap();
173 assert_eq!(reparsed, parsed);
174 }
175 }
176
177 #[test]
178 fn serde_loopback_endpoint() {
179 let ep = LoopbackEndpoint::new("9199").with_context("default");
180 let json = serde_json::to_string(&ep).unwrap();
181 let parsed: LoopbackEndpoint = serde_json::from_str(&json).unwrap();
182 assert_eq!(parsed, ep);
183 }
184}