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::{BlockParse, DialStringCarrier, DialStringTarget, 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
80#[derive(Debug, Clone, Copy)]
83pub struct BridgeDialStringDisplay<'a> {
84 bridge: &'a BridgeDialString,
85 block_parse: BlockParse,
86}
87
88impl fmt::Display for BridgeDialStringDisplay<'_> {
89 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
90 self.bridge
91 .write_with(f, self.block_parse)
92 }
93}
94
95impl fmt::Display for BridgeDialString {
96 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
97 self.write_with(f, BlockParse::default())
98 }
99}
100
101impl FromStr for BridgeDialString {
102 type Err = OriginateError;
103
104 fn from_str(s: &str) -> Result<Self, Self::Err> {
105 Self::parse_with(s, BlockParse::default())
106 }
107}
108
109impl BridgeDialString {
110 pub fn display_with(&self, block_parse: BlockParse) -> BridgeDialStringDisplay<'_> {
113 BridgeDialStringDisplay {
114 bridge: self,
115 block_parse,
116 }
117 }
118
119 fn write_with(&self, f: &mut fmt::Formatter<'_>, block_parse: BlockParse) -> fmt::Result {
120 let target = DialStringTarget::new(CARRIER).with_block_parse(block_parse);
121 if let Some(vars) = &self.variables {
122 if !vars.is_empty() {
123 write!(f, "{}", vars.display_for(target))?;
124 }
125 }
126 for (gi, group) in self
127 .groups
128 .iter()
129 .enumerate()
130 {
131 if gi > 0 {
132 f.write_str("|")?;
133 }
134 for (ei, ep) in group
135 .iter()
136 .enumerate()
137 {
138 if ei > 0 {
139 f.write_str(",")?;
140 }
141 write!(f, "{}", ep.display_for(target))?;
142 }
143 }
144 Ok(())
145 }
146
147 pub fn parse_with(s: &str, block_parse: BlockParse) -> Result<Self, OriginateError> {
150 let target = DialStringTarget::new(CARRIER).with_block_parse(block_parse);
151 let s = s.trim();
152 if s.is_empty() {
153 return Err(OriginateError::ParseError(
154 "empty bridge dial string".into(),
155 ));
156 }
157
158 let (variables, rest) = extract_scoped_variables(s, target, GLOBAL_SCOPES)?;
159
160 let group_strs = split_respecting_brackets(rest, '|');
162 let mut groups = Vec::new();
163 for group_str in &group_strs {
164 let group_str = group_str.trim();
165 if group_str.is_empty() {
166 continue;
167 }
168 let ep_strs = split_respecting_brackets(group_str, ',');
170 let mut endpoints = Vec::new();
171 for ep_str in &ep_strs {
172 let ep_str = ep_str.trim();
173 if ep_str.is_empty() {
174 continue;
175 }
176 let ep = Endpoint::parse_for(ep_str, CARRIER)?;
177 endpoints.push(ep);
178 }
179 if !endpoints.is_empty() {
180 groups.push(endpoints);
181 }
182 }
183
184 Ok(Self { variables, groups })
185 }
186}
187
188fn split_respecting_brackets(s: &str, sep: char) -> Vec<&str> {
191 let mut parts = Vec::new();
192 let mut depth = 0i32;
193 let mut start = 0;
194 let bytes = s.as_bytes();
195
196 for (i, &b) in bytes
197 .iter()
198 .enumerate()
199 {
200 match b {
201 b'{' | b'[' | b'<' | b'(' => depth += 1,
202 b'}' | b']' | b'>' | b')' => {
203 depth -= 1;
204 if depth < 0 {
205 depth = 0;
206 }
207 }
208 _ if b == sep as u8 && depth == 0 => {
209 parts.push(&s[start..i]);
210 start = i + 1;
211 }
212 _ => {}
213 }
214 }
215 parts.push(&s[start..]);
216 parts
217}
218
219#[cfg(test)]
220mod tests {
221 use super::*;
222 use crate::commands::endpoint::{
223 DialString, ErrorEndpoint, LoopbackEndpoint, SofiaEndpoint, SofiaGateway,
224 };
225 use crate::commands::variables::{BlockParse, VariablesType};
226
227 #[test]
231 fn display_and_from_str_are_the_default_revision() {
232 let mut vars = Variables::new(VariablesType::Default);
233 vars.insert("cid", "it's");
234 let mut ep_vars = Variables::new(VariablesType::Channel);
235 ep_vars.insert("path", r"C:\path");
236 let bridge = BridgeDialString::new(vec![vec![SofiaGateway::new("gw", "1234")
237 .with_variables(ep_vars)
238 .into()]])
239 .with_variables(vars);
240
241 let rendered = bridge
242 .display_with(BlockParse::PairSplitCleans)
243 .to_string();
244 assert_eq!(rendered, bridge.to_string());
245 assert_eq!(
246 BridgeDialString::parse_with(&rendered, BlockParse::PairSplitCleans)
247 .unwrap_or_else(|e| panic!("{rendered} failed to parse: {e}")),
248 bridge
249 );
250 }
251
252 #[test]
256 fn bridge_renders_one_level_shallower_than_the_default() {
257 let mut vars = Variables::new(VariablesType::Default);
258 vars.insert("cid", "it's");
259 let ep = SofiaGateway::new("gw", "18005551234");
260 let bridge = BridgeDialString::new(vec![vec![ep.into()]]).with_variables(vars.clone());
261
262 assert_eq!(
263 bridge.to_string(),
264 r"{cid=it\\\\\\'s}sofia/gateway/gw/18005551234"
265 );
266 assert_eq!(vars.to_string(), r"{cid=it\\\\\\\'s}");
267 }
268
269 #[test]
273 fn per_endpoint_escaped_values_round_trip() {
274 let mut ep_vars = Variables::new(VariablesType::Channel);
275 ep_vars.insert("path", r"C:\path");
276 ep_vars.insert("other", "a,b");
277 let bridge = BridgeDialString::new(vec![vec![SofiaGateway::new("gw", "1234")
278 .with_variables(ep_vars)
279 .into()]]);
280
281 let rendered = bridge.to_string();
282 let back: BridgeDialString = rendered
283 .parse()
284 .unwrap_or_else(|e| panic!("{rendered} failed to parse: {e}"));
285 assert_eq!(back, bridge, "rendered {rendered}");
286 }
287
288 #[test]
292 fn per_endpoint_quoted_value_is_refused() {
293 let err = r"[cid=it\\\\\\'s]sofia/gateway/gw/1234"
294 .parse::<BridgeDialString>()
295 .unwrap_err()
296 .to_string();
297 assert!(
298 err.contains("cid") && err.contains("channel scope"),
299 "error does not name the variable and scope: {err}"
300 );
301 }
302
303 #[test]
304 fn display_single_endpoint() {
305 let bridge =
306 BridgeDialString::new(vec![vec![
307 SofiaGateway::new("my_provider", "18005551234").into()
308 ]]);
309 assert_eq!(bridge.to_string(), "sofia/gateway/my_provider/18005551234");
310 }
311
312 #[test]
313 fn display_simultaneous_ring() {
314 let bridge = BridgeDialString::new(vec![vec![
315 SofiaGateway::new("primary", "18005551234").into(),
316 SofiaGateway::new("secondary", "18005551234").into(),
317 ]]);
318 assert_eq!(
319 bridge.to_string(),
320 "sofia/gateway/primary/18005551234,sofia/gateway/secondary/18005551234"
321 );
322 }
323
324 #[test]
325 fn display_sequential_failover() {
326 let bridge = BridgeDialString::new(vec![
327 vec![SofiaGateway::new("primary", "18005551234").into()],
328 vec![SofiaGateway::new("backup", "18005551234").into()],
329 ]);
330 assert_eq!(
331 bridge.to_string(),
332 "sofia/gateway/primary/18005551234|sofia/gateway/backup/18005551234"
333 );
334 }
335
336 #[test]
337 fn display_mixed_simultaneous_and_sequential() {
338 let bridge = BridgeDialString::new(vec![
339 vec![
340 SofiaGateway::new("primary", "1234").into(),
341 SofiaGateway::new("secondary", "1234").into(),
342 ],
343 vec![SofiaGateway::new("backup", "1234").into()],
344 ]);
345 assert_eq!(
346 bridge.to_string(),
347 "sofia/gateway/primary/1234,sofia/gateway/secondary/1234|sofia/gateway/backup/1234"
348 );
349 }
350
351 #[test]
352 fn display_with_global_variables() {
353 let mut vars = Variables::new(VariablesType::Default);
354 vars.insert("hangup_after_bridge", "true");
355 let bridge =
356 BridgeDialString::new(vec![vec![
357 SofiaEndpoint::new("internal", "1000@domain").into()
358 ]])
359 .with_variables(vars);
360 assert_eq!(
361 bridge.to_string(),
362 "{hangup_after_bridge=true}sofia/internal/1000@domain"
363 );
364 }
365
366 #[test]
367 fn display_with_per_endpoint_variables() {
368 let mut ep_vars = Variables::new(VariablesType::Channel);
369 ep_vars.insert("leg_timeout", "30");
370 let bridge = BridgeDialString::new(vec![vec![
371 SofiaGateway::new("gw1", "1234")
372 .with_variables(ep_vars)
373 .into(),
374 SofiaGateway::new("gw2", "1234").into(),
375 ]]);
376 assert_eq!(
377 bridge.to_string(),
378 "[leg_timeout=30]sofia/gateway/gw1/1234,sofia/gateway/gw2/1234"
379 );
380 }
381
382 #[test]
383 fn display_with_error_endpoint_failover() {
384 let bridge = BridgeDialString::new(vec![
385 vec![SofiaGateway::new("primary", "1234").into()],
386 vec![ErrorEndpoint::new(crate::channel::HangupCause::UserBusy).into()],
387 ]);
388 assert_eq!(
389 bridge.to_string(),
390 "sofia/gateway/primary/1234|error/USER_BUSY"
391 );
392 }
393
394 #[test]
395 fn display_with_loopback() {
396 let bridge = BridgeDialString::new(vec![vec![LoopbackEndpoint::new("9199")
397 .with_context("default")
398 .into()]]);
399 assert_eq!(bridge.to_string(), "loopback/9199/default");
400 }
401
402 #[test]
405 fn from_str_single_endpoint() {
406 let bridge: BridgeDialString = "sofia/gateway/my_provider/18005551234"
407 .parse()
408 .unwrap();
409 assert_eq!(
410 bridge
411 .groups()
412 .len(),
413 1
414 );
415 assert_eq!(bridge.groups()[0].len(), 1);
416 assert!(bridge
417 .variables()
418 .is_none());
419 }
420
421 #[test]
422 fn from_str_simultaneous_ring() {
423 let bridge: BridgeDialString = "sofia/gateway/primary/1234,sofia/gateway/secondary/1234"
424 .parse()
425 .unwrap();
426 assert_eq!(
427 bridge
428 .groups()
429 .len(),
430 1
431 );
432 assert_eq!(bridge.groups()[0].len(), 2);
433 }
434
435 #[test]
436 fn from_str_sequential_failover() {
437 let bridge: BridgeDialString = "sofia/gateway/primary/1234|sofia/gateway/backup/1234"
438 .parse()
439 .unwrap();
440 assert_eq!(
441 bridge
442 .groups()
443 .len(),
444 2
445 );
446 assert_eq!(bridge.groups()[0].len(), 1);
447 assert_eq!(bridge.groups()[1].len(), 1);
448 }
449
450 #[test]
451 fn from_str_mixed() {
452 let bridge: BridgeDialString =
453 "sofia/gateway/primary/1234,sofia/gateway/secondary/1234|sofia/gateway/backup/1234"
454 .parse()
455 .unwrap();
456 assert_eq!(
457 bridge
458 .groups()
459 .len(),
460 2
461 );
462 assert_eq!(bridge.groups()[0].len(), 2);
463 assert_eq!(bridge.groups()[1].len(), 1);
464 }
465
466 #[test]
467 fn from_str_with_global_variables() {
468 let bridge: BridgeDialString = "{hangup_after_bridge=true}sofia/internal/1000@domain"
469 .parse()
470 .unwrap();
471 assert!(bridge
472 .variables()
473 .is_some());
474 assert_eq!(
475 bridge
476 .variables()
477 .unwrap()
478 .get("hangup_after_bridge"),
479 Some("true")
480 );
481 assert_eq!(
482 bridge
483 .groups()
484 .len(),
485 1
486 );
487 assert_eq!(bridge.groups()[0].len(), 1);
488 }
489
490 #[test]
491 fn from_str_with_per_endpoint_variables() {
492 let bridge: BridgeDialString =
493 "[leg_timeout=30]sofia/gateway/gw1/1234,sofia/gateway/gw2/1234"
494 .parse()
495 .unwrap();
496 assert_eq!(
497 bridge
498 .groups()
499 .len(),
500 1
501 );
502 assert_eq!(bridge.groups()[0].len(), 2);
503 let ep = &bridge.groups()[0][0];
504 if let Endpoint::SofiaGateway(gw) = ep {
505 assert!(gw
506 .variables
507 .is_some());
508 } else {
509 panic!("expected SofiaGateway");
510 }
511 }
512
513 #[test]
516 fn from_str_with_enterprise_global_variables() {
517 let bridge: BridgeDialString = "<originate_timeout=60>sofia/internal/1000@domain"
518 .parse()
519 .unwrap();
520 assert_eq!(
521 bridge
522 .variables()
523 .expect("enterprise block is global")
524 .get("originate_timeout"),
525 Some("60")
526 );
527 assert_eq!(bridge.groups()[0].len(), 1);
528 }
529
530 #[test]
533 fn from_str_leaves_a_channel_block_on_its_endpoint() {
534 let bridge: BridgeDialString = "[leg_timeout=30]sofia/gateway/gw1/1234"
535 .parse()
536 .unwrap();
537 assert!(bridge
538 .variables()
539 .is_none());
540 assert!(bridge.groups()[0][0]
541 .variables()
542 .is_some());
543 }
544
545 #[test]
546 fn from_str_round_trip_single() {
547 let input = "sofia/gateway/my_provider/18005551234";
548 let bridge: BridgeDialString = input
549 .parse()
550 .unwrap();
551 assert_eq!(bridge.to_string(), input);
552 }
553
554 #[test]
555 fn from_str_round_trip_mixed() {
556 let input =
557 "sofia/gateway/primary/1234,sofia/gateway/secondary/1234|sofia/gateway/backup/1234";
558 let bridge: BridgeDialString = input
559 .parse()
560 .unwrap();
561 assert_eq!(bridge.to_string(), input);
562 }
563
564 #[test]
565 fn from_str_round_trip_with_global_vars() {
566 let input = "{hangup_after_bridge=true}sofia/internal/1000@domain";
567 let bridge: BridgeDialString = input
568 .parse()
569 .unwrap();
570 assert_eq!(bridge.to_string(), input);
571 }
572
573 #[test]
576 fn serde_round_trip_single() {
577 let bridge =
578 BridgeDialString::new(vec![vec![
579 SofiaGateway::new("my_provider", "18005551234").into()
580 ]]);
581 let json = serde_json::to_string(&bridge).unwrap();
582 let parsed: BridgeDialString = serde_json::from_str(&json).unwrap();
583 assert_eq!(bridge, parsed);
584 }
585
586 #[test]
587 fn serde_round_trip_multi_group() {
588 let mut vars = Variables::new(VariablesType::Default);
589 vars.insert("hangup_after_bridge", "true");
590 let bridge = BridgeDialString::new(vec![
591 vec![
592 SofiaGateway::new("primary", "1234").into(),
593 SofiaGateway::new("secondary", "1234").into(),
594 ],
595 vec![ErrorEndpoint::new(crate::channel::HangupCause::UserBusy).into()],
596 ])
597 .with_variables(vars);
598 let json = serde_json::to_string(&bridge).unwrap();
599 let parsed: BridgeDialString = serde_json::from_str(&json).unwrap();
600 assert_eq!(bridge, parsed);
601 }
602
603 #[test]
606 fn from_str_empty_string_rejected() {
607 let result = "".parse::<BridgeDialString>();
608 assert!(result.is_err());
609 }
610
611 #[test]
612 fn from_str_whitespace_only_rejected() {
613 let result = " ".parse::<BridgeDialString>();
614 assert!(result.is_err());
615 }
616
617 #[test]
618 fn from_str_empty_groups_from_trailing_pipe() {
619 let bridge: BridgeDialString = "sofia/gateway/gw1/1234|"
621 .parse()
622 .unwrap();
623 assert_eq!(
624 bridge
625 .groups()
626 .len(),
627 1
628 );
629 }
630
631 #[test]
632 fn from_str_empty_variable_block() {
633 let bridge: BridgeDialString = "{}sofia/gateway/gw1/1234"
634 .parse()
635 .unwrap();
636 assert!(bridge
637 .variables()
638 .is_none());
639 assert_eq!(
640 bridge
641 .groups()
642 .len(),
643 1
644 );
645 }
646
647 #[test]
648 fn from_str_mismatched_bracket_rejected() {
649 let result = "{unclosed=true sofia/gateway/gw1/1234".parse::<BridgeDialString>();
650 assert!(result.is_err());
651 }
652
653 #[test]
654 fn serde_to_display_wire_format() {
655 let json = r#"{
656 "groups": [[{
657 "sofia_gateway": {
658 "gateway": "my_gw",
659 "destination": "18005551234"
660 }
661 }]]
662 }"#;
663 let bridge: BridgeDialString = serde_json::from_str(json).unwrap();
664 assert_eq!(bridge.to_string(), "sofia/gateway/my_gw/18005551234");
665 }
666}