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