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