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