1use indexmap::IndexMap;
4use std::fmt;
5use std::str::FromStr;
6
7use super::originate::OriginateError;
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
16#[cfg_attr(feature = "serde", serde(rename_all = "lowercase"))]
17#[non_exhaustive]
18pub enum VariablesType {
19 Enterprise,
21 Default,
23 Channel,
25}
26
27impl VariablesType {
28 pub(super) fn delimiters(self) -> (char, char) {
29 match self {
30 Self::Enterprise => ('<', '>'),
31 Self::Default => ('{', '}'),
32 Self::Channel => ('[', ']'),
33 }
34 }
35
36 fn backslash_escape(self) -> &'static str {
37 match self {
38 Self::Enterprise | Self::Default => BACKSLASH,
39 Self::Channel => BACKSLASH_CHANNEL,
40 }
41 }
42}
43
44#[derive(Debug, Clone, PartialEq, Eq)]
62pub struct Variables {
63 vars_type: VariablesType,
64 inner: IndexMap<String, String>,
65 separator: Option<char>,
69}
70
71#[derive(Debug, Clone, Copy, PartialEq, Eq)]
79#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
80#[non_exhaustive]
81pub enum DialStringCarrier {
82 EslApi,
85 Dialplan,
88}
89
90const BACKSLASH: &str = r"\\\\\\\\";
93const BACKSLASH_CHANNEL: &str = r"\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\";
96const QUOTE_DIALPLAN: &str = r"\\\\\\'";
98const QUOTE_ESL_API: &str = r"\\\\\\\'";
99
100impl DialStringCarrier {
101 fn quote_escape(self) -> &'static str {
102 match self {
103 Self::EslApi => QUOTE_ESL_API,
104 Self::Dialplan => QUOTE_DIALPLAN,
105 }
106 }
107}
108
109fn check_representable(
119 key: &str,
120 value: &str,
121 vars_type: VariablesType,
122) -> Result<(), OriginateError> {
123 if vars_type == VariablesType::Channel && value.contains('\'') {
124 return Err(OriginateError::ParseError(format!(
125 "variable {key} carries a single quote in channel scope: the switch \
126 pairs it with the next quote in the dial string before it parses \
127 the block, whatever escaping precedes either. Use default scope, \
128 or keep the quote out of the value"
129 )));
130 }
131 if value.is_empty() {
132 return Err(OriginateError::ParseError(format!(
133 "variable {key} has an empty value: the switch discards such a pair \
134 without logging it, so it cannot be told from an absent variable on \
135 the wire. Give it a value or remove it -- and if its presence was \
136 itself the signal, that signal needs a home outside the dial string"
137 )));
138 }
139
140 let (open, close) = vars_type.delimiters();
141 let mut depth = 0i32;
142 for ch in value.chars() {
143 if ch == open {
144 depth += 1;
145 } else if ch == close {
146 depth -= 1;
147 if depth < 0 {
148 return Err(OriginateError::ParseError(format!(
149 "variable {key} closes a '{open}' it never opened, ending the block early"
150 )));
151 }
152 }
153 }
154 if depth != 0 {
155 return Err(OriginateError::ParseError(format!(
156 "variable {key} opens a '{open}' it never closes, swallowing the block's end"
157 )));
158 }
159 Ok(())
160}
161
162fn check_separator(sep: char, vars_type: VariablesType) -> Result<(), OriginateError> {
168 let (open, close) = vars_type.delimiters();
169 if sep == open
170 || sep == close
171 || sep == '='
172 || sep == '^'
173 || (sep == '|' && vars_type == VariablesType::Channel)
174 {
175 return Err(OriginateError::ParseError(format!(
176 "invalid ^^ separator: '{sep}'"
177 )));
178 }
179 Ok(())
180}
181
182fn escape_value(
187 value: &str,
188 carrier: DialStringCarrier,
189 commas_separate: bool,
190 vars_type: VariablesType,
191) -> String {
192 let escaped = value
195 .replace('\\', vars_type.backslash_escape())
196 .replace('\'', carrier.quote_escape());
197 let escaped = if commas_separate {
198 escaped.replace(',', "\\,")
199 } else {
200 escaped
201 };
202 let escaped = if vars_type == VariablesType::Channel {
203 escaped.replace('|', "\\|")
204 } else {
205 escaped
206 };
207 if escaped.contains(' ') {
208 format!("'{}'", escaped)
209 } else {
210 escaped
211 }
212}
213
214fn unescape_value(
218 value: &str,
219 carrier: DialStringCarrier,
220 commas_separate: bool,
221 vars_type: VariablesType,
222) -> String {
223 let s = value
224 .strip_prefix('\'')
225 .and_then(|s| s.strip_suffix('\''))
226 .unwrap_or(value);
227
228 let s = if vars_type == VariablesType::Channel {
229 s.replace("\\|", "|")
230 } else {
231 s.to_string()
232 };
233 let s = if commas_separate {
234 s.replace("\\,", ",")
235 } else {
236 s
237 };
238 s.replace(carrier.quote_escape(), "'")
239 .replace(vars_type.backslash_escape(), "\\")
240}
241
242impl Variables {
243 pub fn new(vars_type: VariablesType) -> Self {
245 Self {
246 vars_type,
247 inner: IndexMap::new(),
248 separator: None,
249 }
250 }
251
252 pub fn with_vars(
254 vars_type: VariablesType,
255 vars: impl IntoIterator<Item = (impl Into<String>, impl Into<String>)>,
256 ) -> Self {
257 Self {
258 vars_type,
259 inner: vars
260 .into_iter()
261 .map(|(k, v)| (k.into(), v.into()))
262 .collect(),
263 separator: None,
264 }
265 }
266
267 pub fn with_separator(mut self, sep: char) -> Result<Self, OriginateError> {
281 check_separator(sep, self.vars_type)?;
282 if let Some((key, _)) = self
283 .inner
284 .iter()
285 .find(|(_, v)| v.contains(sep))
286 {
287 return Err(OriginateError::ParseError(format!(
288 "variable {key} contains the chosen '{sep}' separator"
289 )));
290 }
291 self.separator = Some(sep);
292 Ok(self)
293 }
294
295 pub fn separator(&self) -> Option<char> {
297 self.separator
298 }
299
300 pub fn insert(&mut self, key: impl Into<String>, value: impl Into<String>) {
302 self.inner
303 .insert(key.into(), value.into());
304 }
305
306 pub fn remove(&mut self, key: &str) -> Option<String> {
308 self.inner
309 .shift_remove(key)
310 }
311
312 pub fn get(&self, key: &str) -> Option<&str> {
314 self.inner
315 .get(key)
316 .map(|s| s.as_str())
317 }
318
319 pub fn is_empty(&self) -> bool {
321 self.inner
322 .is_empty()
323 }
324
325 pub fn len(&self) -> usize {
327 self.inner
328 .len()
329 }
330
331 pub fn scope(&self) -> VariablesType {
333 self.vars_type
334 }
335
336 pub fn set_scope(&mut self, scope: VariablesType) {
338 self.vars_type = scope;
339 }
340
341 pub fn iter(&self) -> impl Iterator<Item = (&str, &str)> {
343 self.inner
344 .iter()
345 .map(|(k, v)| (k.as_str(), v.as_str()))
346 }
347
348 pub fn iter_mut(&mut self) -> impl Iterator<Item = (&str, &mut String)> {
350 self.inner
351 .iter_mut()
352 .map(|(k, v)| (k.as_str(), v))
353 }
354
355 pub fn values_mut(&mut self) -> impl Iterator<Item = &mut String> {
357 self.inner
358 .values_mut()
359 }
360}
361
362#[cfg(feature = "serde")]
363impl serde::Serialize for Variables {
364 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
365 if self.vars_type == VariablesType::Default
366 && self
367 .separator
368 .is_none()
369 {
370 self.inner
371 .serialize(serializer)
372 } else {
373 use serde::ser::SerializeStruct;
374 let fields = 2 + usize::from(
375 self.separator
376 .is_some(),
377 );
378 let mut s = serializer.serialize_struct("Variables", fields)?;
379 s.serialize_field("scope", &self.vars_type)?;
380 s.serialize_field("vars", &self.inner)?;
381 if let Some(sep) = self.separator {
382 s.serialize_field("separator", &sep)?;
383 }
384 s.end()
385 }
386 }
387}
388
389#[cfg(feature = "serde")]
390impl<'de> serde::Deserialize<'de> for Variables {
391 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
392 #[derive(serde::Deserialize)]
393 #[serde(untagged)]
394 enum VariablesRepr {
395 Scoped {
396 scope: VariablesType,
397 vars: IndexMap<String, String>,
398 #[serde(default)]
399 separator: Option<char>,
400 },
401 Flat(IndexMap<String, String>),
402 }
403
404 let (vars_type, inner, separator) = match VariablesRepr::deserialize(deserializer)? {
405 VariablesRepr::Scoped {
406 scope,
407 vars,
408 separator,
409 } => (scope, vars, separator),
410 VariablesRepr::Flat(map) => (VariablesType::Default, map, None),
411 };
412 for (key, value) in &inner {
415 check_representable(key, value, vars_type).map_err(serde::de::Error::custom)?;
416 }
417 let vars = Self {
418 vars_type,
419 inner,
420 separator: None,
421 };
422 match separator {
423 Some(sep) => vars
424 .with_separator(sep)
425 .map_err(serde::de::Error::custom),
426 None => Ok(vars),
427 }
428 }
429}
430
431#[derive(Debug, Clone, Copy)]
434pub struct VariablesDisplay<'a> {
435 vars: &'a Variables,
436 carrier: DialStringCarrier,
437}
438
439impl fmt::Display for VariablesDisplay<'_> {
440 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
441 self.vars
442 .write_for(f, self.carrier)
443 }
444}
445
446impl Variables {
447 pub fn display_for(&self, carrier: DialStringCarrier) -> VariablesDisplay<'_> {
450 VariablesDisplay {
451 vars: self,
452 carrier,
453 }
454 }
455
456 pub(super) fn write_for(
457 &self,
458 f: &mut fmt::Formatter<'_>,
459 carrier: DialStringCarrier,
460 ) -> fmt::Result {
461 let (open, close) = self
462 .vars_type
463 .delimiters();
464 f.write_fmt(format_args!("{}", open))?;
465 if let Some(sep) = self.separator {
466 write!(f, "^^{sep}")?;
467 }
468 let commas_separate = self
471 .separator
472 .is_none();
473 let sep = self
474 .separator
475 .unwrap_or(',');
476 for (i, (key, value)) in self
477 .inner
478 .iter()
479 .enumerate()
480 {
481 if i > 0 {
482 write!(f, "{sep}")?;
483 }
484 let value = escape_value(value, carrier, commas_separate, self.vars_type);
485 write!(f, "{}={}", key, value)?;
486 }
487 f.write_fmt(format_args!("{}", close))
488 }
489}
490
491impl fmt::Display for Variables {
492 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
493 self.write_for(f, DialStringCarrier::EslApi)
494 }
495}
496
497impl FromStr for Variables {
498 type Err = OriginateError;
499
500 fn from_str(s: &str) -> Result<Self, Self::Err> {
501 Self::parse_for(s, DialStringCarrier::EslApi)
502 }
503}
504
505impl Variables {
506 pub fn parse_for(s: &str, carrier: DialStringCarrier) -> Result<Self, OriginateError> {
511 let s = s.trim();
512 if s.len() < 2 {
513 return Err(OriginateError::ParseError(
514 "variable block too short".into(),
515 ));
516 }
517
518 let (vars_type, inner_str) = match (s.as_bytes()[0], s.as_bytes()[s.len() - 1]) {
519 (b'{', b'}') => (VariablesType::Default, &s[1..s.len() - 1]),
520 (b'<', b'>') => (VariablesType::Enterprise, &s[1..s.len() - 1]),
521 (b'[', b']') => (VariablesType::Channel, &s[1..s.len() - 1]),
522 (open, close) => {
523 return Err(OriginateError::ParseError(format!(
524 "unknown variable delimiters: {:?}..{:?}",
525 open as char, close as char
526 )));
527 }
528 };
529
530 let (sep, var_str) = match inner_str.strip_prefix("^^") {
531 Some(rest) => {
532 let sep = rest
533 .chars()
534 .next()
535 .ok_or_else(|| {
536 OriginateError::ParseError("^^ without separator character".into())
537 })?;
538 check_separator(sep, vars_type)?;
539 (sep, &rest[sep.len_utf8()..])
540 }
541 None => (',', inner_str),
542 };
543 let commas_separate = sep == ',';
544
545 let mut inner = IndexMap::new();
546 if !var_str.is_empty() {
547 for (i, part) in split_unescaped(var_str, sep)
548 .into_iter()
549 .enumerate()
550 {
551 let (key, value) = part
552 .split_once('=')
553 .ok_or_else(|| {
554 OriginateError::ParseError(format!("missing = in variable {i}"))
555 })?;
556 let value = unescape_value(value, carrier, commas_separate, vars_type);
557 check_representable(key, &value, vars_type)?;
558 inner.insert(key.to_string(), value);
559 }
560 }
561
562 Ok(Self {
563 vars_type,
564 inner,
565 separator: None,
566 })
567 }
568}
569
570fn split_unescaped(s: &str, sep: char) -> Vec<&str> {
576 let mut parts = Vec::new();
577 let mut start = 0;
578 let bytes = s.as_bytes();
579
580 for (i, ch) in s.char_indices() {
581 if ch == sep {
582 let mut backslashes = 0;
583 let mut j = i;
584 while j > 0 && bytes[j - 1] == b'\\' {
585 backslashes += 1;
586 j -= 1;
587 }
588 if backslashes % 2 == 0 {
589 parts.push(&s[start..i]);
590 start = i + ch.len_utf8();
591 }
592 }
593 }
594 parts.push(&s[start..]);
595 parts
596}
597
598#[cfg(test)]
599mod tests {
600 use super::*;
601
602 #[test]
606 fn an_empty_value_is_refused_at_the_boundary() {
607 let err = "{k=,after=sentinel}"
608 .parse::<Variables>()
609 .unwrap_err()
610 .to_string();
611 assert!(err.contains('k'), "error does not name the variable: {err}");
612 assert!(
613 err.contains("remove it"),
614 "error does not name a remedy: {err}"
615 );
616 assert!(
619 err.contains("presence"),
620 "error prescribes a fix without allowing that presence meant something: {err}"
621 );
622 }
623
624 #[test]
628 fn an_unbalanced_bracket_is_refused_while_a_balanced_one_is_not() {
629 assert!("{k=oops}extra}"
630 .parse::<Variables>()
631 .is_err());
632
633 let balanced: Variables = "{k=${some_var}}"
634 .parse()
635 .expect("a balanced ${...} is ordinary and must parse");
636 assert_eq!(balanced.get("k"), Some("${some_var}"));
637 }
638
639 #[test]
643 fn chosen_separator_leaves_commas_alone() {
644 let mut vars = Variables::new(VariablesType::Default);
645 vars.insert("codecs", "PCMA,PCMU,G729");
646 let vars = vars
647 .with_separator(':')
648 .unwrap();
649
650 assert_eq!(vars.to_string(), "{^^:codecs=PCMA,PCMU,G729}");
651 assert_eq!(vars.separator(), Some(':'));
652 }
653
654 #[test]
655 fn separator_that_cannot_delimit_the_block_is_refused() {
656 let vars = Variables::new(VariablesType::Channel);
657 for sep in ['[', ']', '=', '^'] {
658 assert!(
659 vars.clone()
660 .with_separator(sep)
661 .is_err(),
662 "accepted {sep:?}"
663 );
664 }
665 }
666
667 #[test]
671 fn the_parser_refuses_every_separator_the_builder_does() {
672 for sep in ['[', ']', '=', '^'] {
673 assert!(
674 format!("[^^{sep}a=1{sep}b=2]")
675 .parse::<Variables>()
676 .is_err(),
677 "parser accepted {sep:?}"
678 );
679 }
680 }
681
682 #[test]
685 fn separator_already_present_in_a_value_is_refused() {
686 let mut vars = Variables::new(VariablesType::Default);
687 vars.insert("uri", "sip:bob@example.com");
688 let err = vars
689 .with_separator(':')
690 .unwrap_err()
691 .to_string();
692 assert!(
693 err.contains("uri"),
694 "error does not name the variable: {err}"
695 );
696 }
697
698 #[test]
703 fn escaping_pins_the_measured_wire_forms() {
704 let cases = [
705 (DialStringCarrier::Dialplan, "it's", r"it\\\\\\'s"),
706 (DialStringCarrier::EslApi, "it's", r"it\\\\\\\'s"),
707 (DialStringCarrier::Dialplan, "l'a'b", r"l\\\\\\'a\\\\\\'b"),
708 (DialStringCarrier::EslApi, "l'a'b", r"l\\\\\\\'a\\\\\\\'b"),
709 (DialStringCarrier::Dialplan, r"a\nb", r"a\\\\\\\\nb"),
710 (DialStringCarrier::EslApi, r"a\nb", r"a\\\\\\\\nb"),
711 (DialStringCarrier::Dialplan, "a,b", r"a\,b"),
712 (DialStringCarrier::EslApi, "a,b", r"a\,b"),
713 (DialStringCarrier::Dialplan, "a|b", "a|b"),
714 (DialStringCarrier::EslApi, "a|b", "a|b"),
715 ];
716 for (carrier, value, want) in cases {
717 assert_eq!(
718 escape_value(value, carrier, true, VariablesType::Default),
719 want,
720 "{value:?} for {carrier:?}"
721 );
722 }
723 }
724
725 #[test]
728 fn channel_scope_escapes_for_the_leg_splits() {
729 let cases = [
730 (
731 DialStringCarrier::Dialplan,
732 r"a\nb",
733 r"a\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\nb",
734 ),
735 (
736 DialStringCarrier::EslApi,
737 r"a\nb",
738 r"a\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\nb",
739 ),
740 (DialStringCarrier::Dialplan, "a|b", r"a\|b"),
741 (DialStringCarrier::EslApi, "a|b", r"a\|b"),
742 (DialStringCarrier::EslApi, "a,b", r"a\,b"),
743 ];
744 for (carrier, value, want) in cases {
745 assert_eq!(
746 escape_value(value, carrier, true, VariablesType::Channel),
747 want,
748 "{value:?} for {carrier:?}"
749 );
750 }
751 }
752
753 #[test]
757 fn a_chosen_separator_changes_only_the_comma() {
758 let cases = [
759 (DialStringCarrier::Dialplan, "it's", r"it\\\\\\'s"),
760 (DialStringCarrier::EslApi, "it's", r"it\\\\\\\'s"),
761 (DialStringCarrier::Dialplan, "l'a'b", r"l\\\\\\'a\\\\\\'b"),
762 (DialStringCarrier::EslApi, "l'a'b", r"l\\\\\\\'a\\\\\\\'b"),
763 (DialStringCarrier::Dialplan, r"a\nb", r"a\\\\\\\\nb"),
764 (DialStringCarrier::EslApi, r"a\nb", r"a\\\\\\\\nb"),
765 (DialStringCarrier::Dialplan, "a,b", "a,b"),
766 (DialStringCarrier::EslApi, "a,b", "a,b"),
767 ];
768 for (carrier, value, want) in cases {
769 assert_eq!(
770 escape_value(value, carrier, false, VariablesType::Default),
771 want,
772 "{value:?} for {carrier:?}"
773 );
774 }
775 }
776
777 #[test]
780 fn display_defaults_to_the_api_carrier() {
781 let mut vars = Variables::new(VariablesType::Default);
782 vars.insert("k", "it's");
783 assert_eq!(vars.to_string(), r"{k=it\\\\\\\'s}");
784 assert_eq!(
785 vars.display_for(DialStringCarrier::Dialplan)
786 .to_string(),
787 r"{k=it\\\\\\'s}"
788 );
789 }
790
791 #[test]
792 fn round_trips_at_either_carrier() {
793 for carrier in [DialStringCarrier::EslApi, DialStringCarrier::Dialplan] {
794 for value in ["it's", r"a\,b", r"C:\path", "a,b", "with space"] {
795 let mut vars = Variables::new(VariablesType::Default);
796 vars.insert("k", value);
797 vars.insert("after", "sentinel");
798 let rendered = vars
799 .display_for(carrier)
800 .to_string();
801
802 let back = Variables::parse_for(&rendered, carrier).unwrap_or_else(|e| {
803 panic!("{value:?} for {carrier:?} rendered {rendered}: {e}")
804 });
805 assert_eq!(back.get("k"), Some(value), "rendered {rendered}");
806 assert_eq!(back.get("after"), Some("sentinel"), "rendered {rendered}");
807 }
808 }
809 }
810
811 #[test]
815 fn separated_block_round_trips_at_either_carrier() {
816 for carrier in [DialStringCarrier::EslApi, DialStringCarrier::Dialplan] {
817 for value in ["it's", "a,b", r"C:\path", r"a\nb", "with space"] {
818 let mut vars = Variables::new(VariablesType::Default);
819 vars.insert("k", value);
820 vars.insert("after", "sentinel");
821 let vars = vars
822 .with_separator('~')
823 .expect("'~' appears in none of these values");
824 let rendered = vars
825 .display_for(carrier)
826 .to_string();
827
828 let back = Variables::parse_for(&rendered, carrier).unwrap_or_else(|e| {
829 panic!("{value:?} for {carrier:?} rendered {rendered}: {e}")
830 });
831 assert_eq!(back.get("k"), Some(value), "rendered {rendered}");
832 assert_eq!(back.get("after"), Some("sentinel"), "rendered {rendered}");
833 }
834 }
835 }
836
837 #[test]
842 fn value_with_backslash_round_trips() {
843 for value in [r"a\,b", r"C:\path", r"trailing\", r"\\", r"a\nb"] {
844 let mut vars = Variables::new(VariablesType::Default);
845 vars.insert("k", value);
846 vars.insert("after", "sentinel");
847 let rendered = vars.to_string();
848
849 let back: Variables = rendered
850 .parse()
851 .unwrap_or_else(|e| {
852 panic!("{value:?} rendered {rendered} and failed to parse: {e}")
853 });
854 assert_eq!(back.get("k"), Some(value), "rendered {rendered}");
855 assert_eq!(
856 back.get("after"),
857 Some("sentinel"),
858 "value {value:?} ate the next variable: {rendered}"
859 );
860 }
861 }
862
863 #[test]
866 fn missing_equals_error_omits_the_fragment() {
867 let msg = "{origination_caller_id_number=15551234567,15550009999}"
868 .parse::<Variables>()
869 .unwrap_err()
870 .to_string();
871 assert!(
872 !msg.contains("15550009999"),
873 "error quoted its input: {msg}"
874 );
875 assert!(
876 msg.contains("variable 1"),
877 "error does not name the part: {msg}"
878 );
879 }
880
881 #[test]
882 fn unknown_delimiters_error_omits_the_block() {
883 let msg = "(origination_caller_id_number=15551234567)"
884 .parse::<Variables>()
885 .unwrap_err()
886 .to_string();
887 assert!(
888 !msg.contains("15551234567"),
889 "error quoted its input: {msg}"
890 );
891 }
892
893 #[test]
894 fn variables_standard_chars() {
895 let mut vars = Variables::new(VariablesType::Default);
896 vars.insert("test_key", "this_value");
897 vars.insert("second", "2");
898 assert_eq!(vars.to_string(), "{test_key=this_value,second=2}");
899 }
900
901 #[test]
902 fn variables_comma_escaped() {
903 let mut vars = Variables::new(VariablesType::Default);
904 vars.insert("test_key", "this,is,a,value");
905 let result = vars.to_string();
906 assert!(result.contains("\\,"));
907 }
908
909 #[test]
910 fn variables_spaces_quoted() {
911 let mut vars = Variables::new(VariablesType::Default);
912 vars.insert("test_key", "this is a value");
913 let result = vars.to_string();
914 assert_eq!(
915 result
916 .matches('\'')
917 .count(),
918 2
919 );
920 }
921
922 #[test]
923 fn variables_single_quote_escaped() {
924 let mut vars = Variables::new(VariablesType::Default);
925 vars.insert("test_key", "let's_this_be_a_value");
926 let result = vars.to_string();
927 assert!(result.contains("\\'"));
928 }
929
930 #[test]
931 fn variables_enterprise_delimiters() {
932 let mut vars = Variables::new(VariablesType::Enterprise);
933 vars.insert("k", "v");
934 let result = vars.to_string();
935 assert!(result.starts_with('<'));
936 assert!(result.ends_with('>'));
937 }
938
939 #[test]
940 fn variables_channel_delimiters() {
941 let mut vars = Variables::new(VariablesType::Channel);
942 vars.insert("k", "v");
943 let result = vars.to_string();
944 assert!(result.starts_with('['));
945 assert!(result.ends_with(']'));
946 }
947
948 #[test]
949 fn variables_default_delimiters() {
950 let mut vars = Variables::new(VariablesType::Default);
951 vars.insert("k", "v");
952 let result = vars.to_string();
953 assert!(result.starts_with('{'));
954 assert!(result.ends_with('}'));
955 }
956
957 #[test]
958 fn variables_parse_round_trip() {
959 let mut vars = Variables::new(VariablesType::Default);
960 vars.insert("origination_caller_id_number", "9005551212");
961 vars.insert("sip_h_Call-Info", "<url>;meta=123,<uri>");
962 let s = vars.to_string();
963 let parsed: Variables = s
964 .parse()
965 .unwrap();
966 assert_eq!(
967 parsed.get("origination_caller_id_number"),
968 Some("9005551212")
969 );
970 assert_eq!(parsed.get("sip_h_Call-Info"), Some("<url>;meta=123,<uri>"));
971 }
972
973 #[test]
974 fn split_unescaped_basic() {
975 assert_eq!(split_unescaped("a,b,c", ','), vec!["a", "b", "c"]);
976 assert_eq!(split_unescaped("a~b~c", '~'), vec!["a", "b", "c"]);
977 }
978
979 #[test]
980 fn split_unescaped_escaped() {
981 assert_eq!(split_unescaped(r"a\,b,c", ','), vec![r"a\,b", "c"]);
982 }
983
984 #[test]
985 fn split_unescaped_double_backslash() {
986 assert_eq!(split_unescaped(r"a\\,b", ','), vec![r"a\\", "b"]);
988 }
989
990 #[test]
991 fn split_unescaped_triple_backslash() {
992 assert_eq!(split_unescaped(r"a\\\,b", ','), vec![r"a\\\,b"]);
994 }
995
996 #[test]
997 fn variables_caret_caret_separator() {
998 let vars: Variables =
999 "[^^:sip_invite_domain=pbx.example.com:presence_id=1211@pbx.example.com]"
1000 .parse()
1001 .unwrap();
1002 assert_eq!(vars.scope(), VariablesType::Channel);
1003 assert_eq!(vars.get("sip_invite_domain"), Some("pbx.example.com"));
1004 assert_eq!(vars.get("presence_id"), Some("1211@pbx.example.com"));
1005 }
1006
1007 #[test]
1008 fn variables_caret_caret_display_uses_canonical_comma() {
1009 let vars: Variables = "[^^:a=1:b=2]"
1010 .parse()
1011 .unwrap();
1012 assert_eq!(vars.to_string(), "[a=1,b=2]");
1013 }
1014
1015 #[test]
1016 fn variables_caret_caret_default_scope() {
1017 let vars: Variables = "{^^|x=1|y=2}"
1018 .parse()
1019 .unwrap();
1020 assert_eq!(vars.scope(), VariablesType::Default);
1021 assert_eq!(vars.get("x"), Some("1"));
1022 assert_eq!(vars.get("y"), Some("2"));
1023 }
1024
1025 #[test]
1026 fn variables_caret_caret_enterprise_scope() {
1027 let vars: Variables = "<^^;a=1;b=2>"
1028 .parse()
1029 .unwrap();
1030 assert_eq!(vars.scope(), VariablesType::Enterprise);
1031 assert_eq!(vars.get("a"), Some("1"));
1032 }
1033
1034 #[test]
1038 fn variables_caret_caret_keeps_an_escaped_comma_literal() {
1039 let vars: Variables = r"[^^:key=val\,ue:other=x]"
1040 .parse()
1041 .unwrap();
1042 assert_eq!(vars.get("key"), Some(r"val\,ue"));
1043 }
1044
1045 #[test]
1046 fn variables_caret_caret_values_with_commas() {
1047 let vars: Variables = "{^^|sip_h_X-Call-Info=<urn:foo>;purpose=bar,<urn:baz>|other=val}"
1048 .parse()
1049 .unwrap();
1050 assert_eq!(
1051 vars.get("sip_h_X-Call-Info"),
1052 Some("<urn:foo>;purpose=bar,<urn:baz>")
1053 );
1054 assert_eq!(vars.get("other"), Some("val"));
1055 }
1056
1057 #[test]
1058 fn variables_caret_caret_empty_vars() {
1059 let vars: Variables = "[^^:]"
1060 .parse()
1061 .unwrap();
1062 assert!(vars.is_empty());
1063 assert_eq!(vars.scope(), VariablesType::Channel);
1064 }
1065
1066 #[test]
1067 fn variables_caret_caret_missing_separator() {
1068 assert!("[^^]"
1069 .parse::<Variables>()
1070 .is_err());
1071 }
1072
1073 #[test]
1074 fn variables_caret_caret_closing_bracket_as_sep() {
1075 assert!("[^^]]"
1076 .parse::<Variables>()
1077 .is_err());
1078 }
1079
1080 #[test]
1081 fn variables_caret_caret_equals_as_sep() {
1082 assert!("[^^=a=1]"
1083 .parse::<Variables>()
1084 .is_err());
1085 }
1086
1087 #[test]
1088 fn variables_from_str_empty_block() {
1089 let result = "{}".parse::<Variables>();
1090 assert!(
1091 result.is_ok(),
1092 "empty variable block should parse successfully"
1093 );
1094 let vars = result.unwrap();
1095 assert!(
1096 vars.is_empty(),
1097 "parsed empty block should have no variables"
1098 );
1099 }
1100
1101 #[test]
1102 fn variables_from_str_empty_channel_block() {
1103 let result = "[]".parse::<Variables>();
1104 assert!(result.is_ok());
1105 let vars = result.unwrap();
1106 assert!(vars.is_empty());
1107 assert_eq!(vars.scope(), VariablesType::Channel);
1108 }
1109
1110 #[test]
1111 fn variables_from_str_empty_enterprise_block() {
1112 let result = "<>".parse::<Variables>();
1113 assert!(result.is_ok());
1114 let vars = result.unwrap();
1115 assert!(vars.is_empty());
1116 assert_eq!(vars.scope(), VariablesType::Enterprise);
1117 }
1118
1119 #[test]
1120 fn serde_variables_type() {
1121 let json = serde_json::to_string(&VariablesType::Enterprise).unwrap();
1122 assert_eq!(json, "\"enterprise\"");
1123 let parsed: VariablesType = serde_json::from_str(&json).unwrap();
1124 assert_eq!(parsed, VariablesType::Enterprise);
1125 }
1126
1127 #[test]
1128 fn serde_variables_flat_default() {
1129 let mut vars = Variables::new(VariablesType::Default);
1130 vars.insert("key1", "val1");
1131 vars.insert("key2", "val2");
1132 let json = serde_json::to_string(&vars).unwrap();
1133 let parsed: Variables = serde_json::from_str(&json).unwrap();
1134 assert_eq!(parsed.scope(), VariablesType::Default);
1135 assert_eq!(parsed.get("key1"), Some("val1"));
1136 assert_eq!(parsed.get("key2"), Some("val2"));
1137 }
1138
1139 #[test]
1140 fn serde_variables_scoped_enterprise() {
1141 let mut vars = Variables::new(VariablesType::Enterprise);
1142 vars.insert("key1", "val1");
1143 let json = serde_json::to_string(&vars).unwrap();
1144 assert!(json.contains("\"enterprise\""));
1145 let parsed: Variables = serde_json::from_str(&json).unwrap();
1146 assert_eq!(parsed.scope(), VariablesType::Enterprise);
1147 assert_eq!(parsed.get("key1"), Some("val1"));
1148 }
1149
1150 #[test]
1151 fn serde_variables_flat_map_deserializes_as_default() {
1152 let json = r#"{"key1":"val1","key2":"val2"}"#;
1153 let vars: Variables = serde_json::from_str(json).unwrap();
1154 assert_eq!(vars.scope(), VariablesType::Default);
1155 assert_eq!(vars.get("key1"), Some("val1"));
1156 assert_eq!(vars.get("key2"), Some("val2"));
1157 }
1158
1159 #[test]
1160 fn serde_variables_scoped_deserializes() {
1161 let json = r#"{"scope":"channel","vars":{"k":"v"}}"#;
1162 let vars: Variables = serde_json::from_str(json).unwrap();
1163 assert_eq!(vars.scope(), VariablesType::Channel);
1164 assert_eq!(vars.get("k"), Some("v"));
1165 }
1166
1167 #[test]
1170 fn serde_variables_separator_round_trips() {
1171 let mut vars = Variables::new(VariablesType::Default);
1172 vars.insert("codecs", "PCMA,PCMU");
1173 let vars = vars
1174 .with_separator('|')
1175 .unwrap();
1176
1177 let json = serde_json::to_string(&vars).unwrap();
1178 let parsed: Variables = serde_json::from_str(&json).unwrap();
1179 assert_eq!(parsed.separator(), Some('|'));
1180 assert_eq!(parsed, vars);
1181 assert_eq!(parsed.to_string(), "{^^|codecs=PCMA,PCMU}");
1182 }
1183
1184 #[test]
1187 fn serde_variables_default_separator_is_absent() {
1188 let mut flat = Variables::new(VariablesType::Default);
1189 flat.insert("k", "v");
1190 assert_eq!(serde_json::to_string(&flat).unwrap(), r#"{"k":"v"}"#);
1191
1192 let mut scoped = Variables::new(VariablesType::Channel);
1193 scoped.insert("k", "v");
1194 assert_eq!(
1195 serde_json::to_string(&scoped).unwrap(),
1196 r#"{"scope":"channel","vars":{"k":"v"}}"#
1197 );
1198 }
1199
1200 #[test]
1201 fn serde_variables_separator_deserializes_from_config() {
1202 let json = r#"{"scope":"channel","vars":{"codecs":"PCMA,PCMU"},"separator":":"}"#;
1203 let vars: Variables = serde_json::from_str(json).unwrap();
1204 assert_eq!(vars.scope(), VariablesType::Channel);
1205 assert_eq!(vars.separator(), Some(':'));
1206 assert_eq!(vars.to_string(), "[^^:codecs=PCMA,PCMU]");
1207 }
1208
1209 #[test]
1212 fn pipe_separator_is_refused_in_channel_scope_only() {
1213 let mut vars = Variables::new(VariablesType::Channel);
1214 vars.insert("k", "v");
1215 assert!(vars
1216 .clone()
1217 .with_separator('|')
1218 .is_err());
1219 let mut vars = Variables::new(VariablesType::Default);
1220 vars.insert("k", "v");
1221 assert!(vars
1222 .with_separator('|')
1223 .is_ok());
1224 }
1225
1226 #[test]
1229 fn serde_variables_unusable_separator_is_refused() {
1230 let undelimitable = r#"{"scope":"default","vars":{"k":"v"},"separator":"="}"#;
1231 assert!(serde_json::from_str::<Variables>(undelimitable).is_err());
1232
1233 let in_a_value =
1234 r#"{"scope":"default","vars":{"uri":"sip:bob@example.com"},"separator":":"}"#;
1235 let err = serde_json::from_str::<Variables>(in_a_value)
1236 .unwrap_err()
1237 .to_string();
1238 assert!(
1239 err.contains("uri"),
1240 "error does not name the variable: {err}"
1241 );
1242 }
1243
1244 #[test]
1249 fn a_quote_in_channel_scope_is_refused_at_every_boundary() {
1250 let err = r"[cid=it\\\\\\\'s]"
1251 .parse::<Variables>()
1252 .unwrap_err()
1253 .to_string();
1254 assert!(
1255 err.contains("cid") && err.contains("channel scope"),
1256 "error does not name the variable and scope: {err}"
1257 );
1258
1259 let err = serde_json::from_str::<Variables>(r#"{"scope":"channel","vars":{"cid":"it's"}}"#)
1260 .unwrap_err()
1261 .to_string();
1262 assert!(
1263 err.contains("cid") && err.contains("channel scope"),
1264 "error does not name the variable and scope: {err}"
1265 );
1266
1267 assert!(r"{cid=it\\\\\\\'s}"
1268 .parse::<Variables>()
1269 .is_ok());
1270 assert!(
1271 serde_json::from_str::<Variables>(r#"{"scope":"default","vars":{"cid":"it's"}}"#)
1272 .is_ok()
1273 );
1274 }
1275}