freeswitch_types/commands/
bridge.rs1use std::fmt;
7use std::str::FromStr;
8
9use super::endpoint::Endpoint;
10use super::find_matching_bracket;
11use super::originate::OriginateError;
12use super::variables::Variables;
13
14#[derive(Debug, Clone, PartialEq, Eq)]
23#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
24pub struct BridgeDialString {
25 #[cfg_attr(
26 feature = "serde",
27 serde(default, skip_serializing_if = "Option::is_none")
28 )]
29 variables: Option<Variables>,
30 groups: Vec<Vec<Endpoint>>,
31}
32
33impl BridgeDialString {
34 pub fn new(groups: Vec<Vec<Endpoint>>) -> Self {
36 Self {
37 variables: None,
38 groups,
39 }
40 }
41
42 pub fn with_variables(mut self, variables: Variables) -> Self {
44 self.variables = Some(variables);
45 self
46 }
47
48 pub fn variables(&self) -> Option<&Variables> {
50 self.variables
51 .as_ref()
52 }
53
54 pub fn groups(&self) -> &[Vec<Endpoint>] {
57 &self.groups
58 }
59
60 pub fn variables_mut(&mut self) -> &mut Option<Variables> {
62 &mut self.variables
63 }
64
65 pub fn groups_mut(&mut self) -> &mut Vec<Vec<Endpoint>> {
67 &mut self.groups
68 }
69}
70
71impl fmt::Display for BridgeDialString {
72 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
73 if let Some(vars) = &self.variables {
74 if !vars.is_empty() {
75 write!(f, "{}", vars)?;
76 }
77 }
78 for (gi, group) in self
79 .groups
80 .iter()
81 .enumerate()
82 {
83 if gi > 0 {
84 f.write_str("|")?;
85 }
86 for (ei, ep) in group
87 .iter()
88 .enumerate()
89 {
90 if ei > 0 {
91 f.write_str(",")?;
92 }
93 write!(f, "{}", ep)?;
94 }
95 }
96 Ok(())
97 }
98}
99
100impl FromStr for BridgeDialString {
101 type Err = OriginateError;
102
103 fn from_str(s: &str) -> Result<Self, Self::Err> {
104 let s = s.trim();
105 if s.is_empty() {
106 return Err(OriginateError::ParseError(
107 "empty bridge dial string".into(),
108 ));
109 }
110
111 let (variables, rest) = if s.starts_with('{') {
113 let close = find_matching_bracket(s, '{', '}').ok_or_else(|| {
114 OriginateError::ParseError("unclosed { in bridge dial string".into())
115 })?;
116 let var_str = &s[..=close];
117 let vars: Variables = var_str.parse()?;
118 let vars = if vars.is_empty() { None } else { Some(vars) };
119 (vars, &s[close + 1..])
120 } else {
121 (None, s)
122 };
123
124 let group_strs = split_respecting_brackets(rest, '|');
126 let mut groups = Vec::new();
127 for group_str in &group_strs {
128 let group_str = group_str.trim();
129 if group_str.is_empty() {
130 continue;
131 }
132 let ep_strs = split_respecting_brackets(group_str, ',');
134 let mut endpoints = Vec::new();
135 for ep_str in &ep_strs {
136 let ep_str = ep_str.trim();
137 if ep_str.is_empty() {
138 continue;
139 }
140 let ep: Endpoint = ep_str.parse()?;
141 endpoints.push(ep);
142 }
143 if !endpoints.is_empty() {
144 groups.push(endpoints);
145 }
146 }
147
148 Ok(Self { variables, groups })
149 }
150}
151
152fn split_respecting_brackets(s: &str, sep: char) -> Vec<&str> {
155 let mut parts = Vec::new();
156 let mut depth = 0i32;
157 let mut start = 0;
158 let bytes = s.as_bytes();
159
160 for (i, &b) in bytes
161 .iter()
162 .enumerate()
163 {
164 match b {
165 b'{' | b'[' | b'<' | b'(' => depth += 1,
166 b'}' | b']' | b'>' | b')' => {
167 depth -= 1;
168 if depth < 0 {
169 depth = 0;
170 }
171 }
172 _ if b == sep as u8 && depth == 0 => {
173 parts.push(&s[start..i]);
174 start = i + 1;
175 }
176 _ => {}
177 }
178 }
179 parts.push(&s[start..]);
180 parts
181}
182
183#[cfg(test)]
184mod tests {
185 use super::*;
186 use crate::commands::endpoint::{ErrorEndpoint, LoopbackEndpoint, SofiaEndpoint, SofiaGateway};
187 use crate::commands::variables::VariablesType;
188
189 #[test]
192 fn display_single_endpoint() {
193 let bridge =
194 BridgeDialString::new(vec![vec![
195 SofiaGateway::new("my_provider", "18005551234").into()
196 ]]);
197 assert_eq!(bridge.to_string(), "sofia/gateway/my_provider/18005551234");
198 }
199
200 #[test]
201 fn display_simultaneous_ring() {
202 let bridge = BridgeDialString::new(vec![vec![
203 SofiaGateway::new("primary", "18005551234").into(),
204 SofiaGateway::new("secondary", "18005551234").into(),
205 ]]);
206 assert_eq!(
207 bridge.to_string(),
208 "sofia/gateway/primary/18005551234,sofia/gateway/secondary/18005551234"
209 );
210 }
211
212 #[test]
213 fn display_sequential_failover() {
214 let bridge = BridgeDialString::new(vec![
215 vec![SofiaGateway::new("primary", "18005551234").into()],
216 vec![SofiaGateway::new("backup", "18005551234").into()],
217 ]);
218 assert_eq!(
219 bridge.to_string(),
220 "sofia/gateway/primary/18005551234|sofia/gateway/backup/18005551234"
221 );
222 }
223
224 #[test]
225 fn display_mixed_simultaneous_and_sequential() {
226 let bridge = BridgeDialString::new(vec![
227 vec![
228 SofiaGateway::new("primary", "1234").into(),
229 SofiaGateway::new("secondary", "1234").into(),
230 ],
231 vec![SofiaGateway::new("backup", "1234").into()],
232 ]);
233 assert_eq!(
234 bridge.to_string(),
235 "sofia/gateway/primary/1234,sofia/gateway/secondary/1234|sofia/gateway/backup/1234"
236 );
237 }
238
239 #[test]
240 fn display_with_global_variables() {
241 let mut vars = Variables::new(VariablesType::Default);
242 vars.insert("hangup_after_bridge", "true");
243 let bridge =
244 BridgeDialString::new(vec![vec![
245 SofiaEndpoint::new("internal", "1000@domain").into()
246 ]])
247 .with_variables(vars);
248 assert_eq!(
249 bridge.to_string(),
250 "{hangup_after_bridge=true}sofia/internal/1000@domain"
251 );
252 }
253
254 #[test]
255 fn display_with_per_endpoint_variables() {
256 let mut ep_vars = Variables::new(VariablesType::Channel);
257 ep_vars.insert("leg_timeout", "30");
258 let bridge = BridgeDialString::new(vec![vec![
259 SofiaGateway::new("gw1", "1234")
260 .with_variables(ep_vars)
261 .into(),
262 SofiaGateway::new("gw2", "1234").into(),
263 ]]);
264 assert_eq!(
265 bridge.to_string(),
266 "[leg_timeout=30]sofia/gateway/gw1/1234,sofia/gateway/gw2/1234"
267 );
268 }
269
270 #[test]
271 fn display_with_error_endpoint_failover() {
272 let bridge = BridgeDialString::new(vec![
273 vec![SofiaGateway::new("primary", "1234").into()],
274 vec![ErrorEndpoint::new(crate::channel::HangupCause::UserBusy).into()],
275 ]);
276 assert_eq!(
277 bridge.to_string(),
278 "sofia/gateway/primary/1234|error/USER_BUSY"
279 );
280 }
281
282 #[test]
283 fn display_with_loopback() {
284 let bridge = BridgeDialString::new(vec![vec![LoopbackEndpoint::new("9199")
285 .with_context("default")
286 .into()]]);
287 assert_eq!(bridge.to_string(), "loopback/9199/default");
288 }
289
290 #[test]
293 fn from_str_single_endpoint() {
294 let bridge: BridgeDialString = "sofia/gateway/my_provider/18005551234"
295 .parse()
296 .unwrap();
297 assert_eq!(
298 bridge
299 .groups()
300 .len(),
301 1
302 );
303 assert_eq!(bridge.groups()[0].len(), 1);
304 assert!(bridge
305 .variables()
306 .is_none());
307 }
308
309 #[test]
310 fn from_str_simultaneous_ring() {
311 let bridge: BridgeDialString = "sofia/gateway/primary/1234,sofia/gateway/secondary/1234"
312 .parse()
313 .unwrap();
314 assert_eq!(
315 bridge
316 .groups()
317 .len(),
318 1
319 );
320 assert_eq!(bridge.groups()[0].len(), 2);
321 }
322
323 #[test]
324 fn from_str_sequential_failover() {
325 let bridge: BridgeDialString = "sofia/gateway/primary/1234|sofia/gateway/backup/1234"
326 .parse()
327 .unwrap();
328 assert_eq!(
329 bridge
330 .groups()
331 .len(),
332 2
333 );
334 assert_eq!(bridge.groups()[0].len(), 1);
335 assert_eq!(bridge.groups()[1].len(), 1);
336 }
337
338 #[test]
339 fn from_str_mixed() {
340 let bridge: BridgeDialString =
341 "sofia/gateway/primary/1234,sofia/gateway/secondary/1234|sofia/gateway/backup/1234"
342 .parse()
343 .unwrap();
344 assert_eq!(
345 bridge
346 .groups()
347 .len(),
348 2
349 );
350 assert_eq!(bridge.groups()[0].len(), 2);
351 assert_eq!(bridge.groups()[1].len(), 1);
352 }
353
354 #[test]
355 fn from_str_with_global_variables() {
356 let bridge: BridgeDialString = "{hangup_after_bridge=true}sofia/internal/1000@domain"
357 .parse()
358 .unwrap();
359 assert!(bridge
360 .variables()
361 .is_some());
362 assert_eq!(
363 bridge
364 .variables()
365 .unwrap()
366 .get("hangup_after_bridge"),
367 Some("true")
368 );
369 assert_eq!(
370 bridge
371 .groups()
372 .len(),
373 1
374 );
375 assert_eq!(bridge.groups()[0].len(), 1);
376 }
377
378 #[test]
379 fn from_str_with_per_endpoint_variables() {
380 let bridge: BridgeDialString =
381 "[leg_timeout=30]sofia/gateway/gw1/1234,sofia/gateway/gw2/1234"
382 .parse()
383 .unwrap();
384 assert_eq!(
385 bridge
386 .groups()
387 .len(),
388 1
389 );
390 assert_eq!(bridge.groups()[0].len(), 2);
391 let ep = &bridge.groups()[0][0];
392 if let Endpoint::SofiaGateway(gw) = ep {
393 assert!(gw
394 .variables
395 .is_some());
396 } else {
397 panic!("expected SofiaGateway");
398 }
399 }
400
401 #[test]
402 fn from_str_round_trip_single() {
403 let input = "sofia/gateway/my_provider/18005551234";
404 let bridge: BridgeDialString = input
405 .parse()
406 .unwrap();
407 assert_eq!(bridge.to_string(), input);
408 }
409
410 #[test]
411 fn from_str_round_trip_mixed() {
412 let input =
413 "sofia/gateway/primary/1234,sofia/gateway/secondary/1234|sofia/gateway/backup/1234";
414 let bridge: BridgeDialString = input
415 .parse()
416 .unwrap();
417 assert_eq!(bridge.to_string(), input);
418 }
419
420 #[test]
421 fn from_str_round_trip_with_global_vars() {
422 let input = "{hangup_after_bridge=true}sofia/internal/1000@domain";
423 let bridge: BridgeDialString = input
424 .parse()
425 .unwrap();
426 assert_eq!(bridge.to_string(), input);
427 }
428
429 #[test]
432 fn serde_round_trip_single() {
433 let bridge =
434 BridgeDialString::new(vec![vec![
435 SofiaGateway::new("my_provider", "18005551234").into()
436 ]]);
437 let json = serde_json::to_string(&bridge).unwrap();
438 let parsed: BridgeDialString = serde_json::from_str(&json).unwrap();
439 assert_eq!(bridge, parsed);
440 }
441
442 #[test]
443 fn serde_round_trip_multi_group() {
444 let mut vars = Variables::new(VariablesType::Default);
445 vars.insert("hangup_after_bridge", "true");
446 let bridge = BridgeDialString::new(vec![
447 vec![
448 SofiaGateway::new("primary", "1234").into(),
449 SofiaGateway::new("secondary", "1234").into(),
450 ],
451 vec![ErrorEndpoint::new(crate::channel::HangupCause::UserBusy).into()],
452 ])
453 .with_variables(vars);
454 let json = serde_json::to_string(&bridge).unwrap();
455 let parsed: BridgeDialString = serde_json::from_str(&json).unwrap();
456 assert_eq!(bridge, parsed);
457 }
458
459 #[test]
462 fn from_str_empty_string_rejected() {
463 let result = "".parse::<BridgeDialString>();
464 assert!(result.is_err());
465 }
466
467 #[test]
468 fn from_str_whitespace_only_rejected() {
469 let result = " ".parse::<BridgeDialString>();
470 assert!(result.is_err());
471 }
472
473 #[test]
474 fn from_str_empty_groups_from_trailing_pipe() {
475 let bridge: BridgeDialString = "sofia/gateway/gw1/1234|"
477 .parse()
478 .unwrap();
479 assert_eq!(
480 bridge
481 .groups()
482 .len(),
483 1
484 );
485 }
486
487 #[test]
488 fn from_str_empty_variable_block() {
489 let bridge: BridgeDialString = "{}sofia/gateway/gw1/1234"
490 .parse()
491 .unwrap();
492 assert!(bridge
493 .variables()
494 .is_none());
495 assert_eq!(
496 bridge
497 .groups()
498 .len(),
499 1
500 );
501 }
502
503 #[test]
504 fn from_str_mismatched_bracket_rejected() {
505 let result = "{unclosed=true sofia/gateway/gw1/1234".parse::<BridgeDialString>();
506 assert!(result.is_err());
507 }
508
509 #[test]
510 fn serde_to_display_wire_format() {
511 let json = r#"{
512 "groups": [[{
513 "sofia_gateway": {
514 "gateway": "my_gw",
515 "destination": "18005551234"
516 }
517 }]]
518 }"#;
519 let bridge: BridgeDialString = serde_json::from_str(json).unwrap();
520 assert_eq!(bridge.to_string(), "sofia/gateway/my_gw/18005551234");
521 }
522}