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
37#[derive(Debug, Clone, PartialEq, Eq)]
55pub struct Variables {
56 vars_type: VariablesType,
57 inner: IndexMap<String, String>,
58 separator: Option<char>,
62}
63
64#[derive(Debug, Clone, Copy, PartialEq, Eq)]
72#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
73#[non_exhaustive]
74pub enum DialStringCarrier {
75 EslApi,
78 Dialplan,
81}
82
83const BACKSLASH: &str = r"\\\\\\\\";
85const QUOTE_DIALPLAN: &str = r"\\'";
87const QUOTE_ESL_API: &str = r"\\\'";
88
89impl DialStringCarrier {
90 fn quote_escape(self) -> &'static str {
91 match self {
92 Self::EslApi => QUOTE_ESL_API,
93 Self::Dialplan => QUOTE_DIALPLAN,
94 }
95 }
96}
97
98fn check_representable(
106 key: &str,
107 value: &str,
108 vars_type: VariablesType,
109) -> Result<(), OriginateError> {
110 if value.is_empty() {
111 return Err(OriginateError::ParseError(format!(
112 "variable {key} has an empty value: the switch discards such a pair \
113 without logging it, so it cannot be told from an absent variable on \
114 the wire. Give it a value or remove it -- and if its presence was \
115 itself the signal, that signal needs a home outside the dial string"
116 )));
117 }
118
119 let (open, close) = vars_type.delimiters();
120 let mut depth = 0i32;
121 for ch in value.chars() {
122 if ch == open {
123 depth += 1;
124 } else if ch == close {
125 depth -= 1;
126 if depth < 0 {
127 return Err(OriginateError::ParseError(format!(
128 "variable {key} closes a '{open}' it never opened, ending the block early"
129 )));
130 }
131 }
132 }
133 if depth != 0 {
134 return Err(OriginateError::ParseError(format!(
135 "variable {key} opens a '{open}' it never closes, swallowing the block's end"
136 )));
137 }
138 Ok(())
139}
140
141fn check_separator(sep: char, vars_type: VariablesType) -> Result<(), OriginateError> {
146 let (open, close) = vars_type.delimiters();
147 if sep == open || sep == close || sep == '=' || sep == '^' {
148 return Err(OriginateError::ParseError(format!(
149 "invalid ^^ separator: '{sep}'"
150 )));
151 }
152 Ok(())
153}
154
155fn escape_value(value: &str, carrier: DialStringCarrier, commas_separate: bool) -> String {
159 let escaped = value
162 .replace('\\', BACKSLASH)
163 .replace('\'', carrier.quote_escape());
164 let escaped = if commas_separate {
165 escaped.replace(',', "\\,")
166 } else {
167 escaped
168 };
169 if escaped.contains(' ') {
170 format!("'{}'", escaped)
171 } else {
172 escaped
173 }
174}
175
176fn unescape_value(value: &str, carrier: DialStringCarrier, commas_separate: bool) -> String {
180 let s = value
181 .strip_prefix('\'')
182 .and_then(|s| s.strip_suffix('\''))
183 .unwrap_or(value);
184
185 let s = if commas_separate {
186 s.replace("\\,", ",")
187 } else {
188 s.to_string()
189 };
190 s.replace(carrier.quote_escape(), "'")
191 .replace(BACKSLASH, "\\")
192}
193
194impl Variables {
195 pub fn new(vars_type: VariablesType) -> Self {
197 Self {
198 vars_type,
199 inner: IndexMap::new(),
200 separator: None,
201 }
202 }
203
204 pub fn with_vars(
206 vars_type: VariablesType,
207 vars: impl IntoIterator<Item = (impl Into<String>, impl Into<String>)>,
208 ) -> Self {
209 Self {
210 vars_type,
211 inner: vars
212 .into_iter()
213 .map(|(k, v)| (k.into(), v.into()))
214 .collect(),
215 separator: None,
216 }
217 }
218
219 pub fn with_separator(mut self, sep: char) -> Result<Self, OriginateError> {
233 check_separator(sep, self.vars_type)?;
234 if let Some((key, _)) = self
235 .inner
236 .iter()
237 .find(|(_, v)| v.contains(sep))
238 {
239 return Err(OriginateError::ParseError(format!(
240 "variable {key} contains the chosen '{sep}' separator"
241 )));
242 }
243 self.separator = Some(sep);
244 Ok(self)
245 }
246
247 pub fn separator(&self) -> Option<char> {
249 self.separator
250 }
251
252 pub fn insert(&mut self, key: impl Into<String>, value: impl Into<String>) {
254 self.inner
255 .insert(key.into(), value.into());
256 }
257
258 pub fn remove(&mut self, key: &str) -> Option<String> {
260 self.inner
261 .shift_remove(key)
262 }
263
264 pub fn get(&self, key: &str) -> Option<&str> {
266 self.inner
267 .get(key)
268 .map(|s| s.as_str())
269 }
270
271 pub fn is_empty(&self) -> bool {
273 self.inner
274 .is_empty()
275 }
276
277 pub fn len(&self) -> usize {
279 self.inner
280 .len()
281 }
282
283 pub fn scope(&self) -> VariablesType {
285 self.vars_type
286 }
287
288 pub fn set_scope(&mut self, scope: VariablesType) {
290 self.vars_type = scope;
291 }
292
293 pub fn iter(&self) -> impl Iterator<Item = (&str, &str)> {
295 self.inner
296 .iter()
297 .map(|(k, v)| (k.as_str(), v.as_str()))
298 }
299
300 pub fn iter_mut(&mut self) -> impl Iterator<Item = (&str, &mut String)> {
302 self.inner
303 .iter_mut()
304 .map(|(k, v)| (k.as_str(), v))
305 }
306
307 pub fn values_mut(&mut self) -> impl Iterator<Item = &mut String> {
309 self.inner
310 .values_mut()
311 }
312}
313
314#[cfg(feature = "serde")]
315impl serde::Serialize for Variables {
316 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
317 if self.vars_type == VariablesType::Default
318 && self
319 .separator
320 .is_none()
321 {
322 self.inner
323 .serialize(serializer)
324 } else {
325 use serde::ser::SerializeStruct;
326 let fields = 2 + usize::from(
327 self.separator
328 .is_some(),
329 );
330 let mut s = serializer.serialize_struct("Variables", fields)?;
331 s.serialize_field("scope", &self.vars_type)?;
332 s.serialize_field("vars", &self.inner)?;
333 if let Some(sep) = self.separator {
334 s.serialize_field("separator", &sep)?;
335 }
336 s.end()
337 }
338 }
339}
340
341#[cfg(feature = "serde")]
342impl<'de> serde::Deserialize<'de> for Variables {
343 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
344 #[derive(serde::Deserialize)]
345 #[serde(untagged)]
346 enum VariablesRepr {
347 Scoped {
348 scope: VariablesType,
349 vars: IndexMap<String, String>,
350 #[serde(default)]
351 separator: Option<char>,
352 },
353 Flat(IndexMap<String, String>),
354 }
355
356 let (vars_type, inner, separator) = match VariablesRepr::deserialize(deserializer)? {
357 VariablesRepr::Scoped {
358 scope,
359 vars,
360 separator,
361 } => (scope, vars, separator),
362 VariablesRepr::Flat(map) => (VariablesType::Default, map, None),
363 };
364 for (key, value) in &inner {
367 check_representable(key, value, vars_type).map_err(serde::de::Error::custom)?;
368 }
369 let vars = Self {
370 vars_type,
371 inner,
372 separator: None,
373 };
374 match separator {
375 Some(sep) => vars
376 .with_separator(sep)
377 .map_err(serde::de::Error::custom),
378 None => Ok(vars),
379 }
380 }
381}
382
383#[derive(Debug, Clone, Copy)]
386pub struct VariablesDisplay<'a> {
387 vars: &'a Variables,
388 carrier: DialStringCarrier,
389}
390
391impl fmt::Display for VariablesDisplay<'_> {
392 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
393 self.vars
394 .write_for(f, self.carrier)
395 }
396}
397
398impl Variables {
399 pub fn display_for(&self, carrier: DialStringCarrier) -> VariablesDisplay<'_> {
402 VariablesDisplay {
403 vars: self,
404 carrier,
405 }
406 }
407
408 pub(super) fn write_for(
409 &self,
410 f: &mut fmt::Formatter<'_>,
411 carrier: DialStringCarrier,
412 ) -> fmt::Result {
413 let (open, close) = self
414 .vars_type
415 .delimiters();
416 f.write_fmt(format_args!("{}", open))?;
417 if let Some(sep) = self.separator {
418 write!(f, "^^{sep}")?;
419 }
420 let commas_separate = self
423 .separator
424 .is_none();
425 let sep = self
426 .separator
427 .unwrap_or(',');
428 for (i, (key, value)) in self
429 .inner
430 .iter()
431 .enumerate()
432 {
433 if i > 0 {
434 write!(f, "{sep}")?;
435 }
436 let value = escape_value(value, carrier, commas_separate);
437 write!(f, "{}={}", key, value)?;
438 }
439 f.write_fmt(format_args!("{}", close))
440 }
441}
442
443impl fmt::Display for Variables {
444 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
445 self.write_for(f, DialStringCarrier::EslApi)
446 }
447}
448
449impl FromStr for Variables {
450 type Err = OriginateError;
451
452 fn from_str(s: &str) -> Result<Self, Self::Err> {
453 Self::parse_for(s, DialStringCarrier::EslApi)
454 }
455}
456
457impl Variables {
458 pub fn parse_for(s: &str, carrier: DialStringCarrier) -> Result<Self, OriginateError> {
463 let s = s.trim();
464 if s.len() < 2 {
465 return Err(OriginateError::ParseError(
466 "variable block too short".into(),
467 ));
468 }
469
470 let (vars_type, inner_str) = match (s.as_bytes()[0], s.as_bytes()[s.len() - 1]) {
471 (b'{', b'}') => (VariablesType::Default, &s[1..s.len() - 1]),
472 (b'<', b'>') => (VariablesType::Enterprise, &s[1..s.len() - 1]),
473 (b'[', b']') => (VariablesType::Channel, &s[1..s.len() - 1]),
474 (open, close) => {
475 return Err(OriginateError::ParseError(format!(
476 "unknown variable delimiters: {:?}..{:?}",
477 open as char, close as char
478 )));
479 }
480 };
481
482 let (sep, var_str) = match inner_str.strip_prefix("^^") {
483 Some(rest) => {
484 let sep = rest
485 .chars()
486 .next()
487 .ok_or_else(|| {
488 OriginateError::ParseError("^^ without separator character".into())
489 })?;
490 check_separator(sep, vars_type)?;
491 (sep, &rest[sep.len_utf8()..])
492 }
493 None => (',', inner_str),
494 };
495 let commas_separate = sep == ',';
496
497 let mut inner = IndexMap::new();
498 if !var_str.is_empty() {
499 for (i, part) in split_unescaped(var_str, sep)
500 .into_iter()
501 .enumerate()
502 {
503 let (key, value) = part
504 .split_once('=')
505 .ok_or_else(|| {
506 OriginateError::ParseError(format!("missing = in variable {i}"))
507 })?;
508 let value = unescape_value(value, carrier, commas_separate);
509 check_representable(key, &value, vars_type)?;
510 inner.insert(key.to_string(), value);
511 }
512 }
513
514 Ok(Self {
515 vars_type,
516 inner,
517 separator: None,
518 })
519 }
520}
521
522fn split_unescaped(s: &str, sep: char) -> Vec<&str> {
528 let mut parts = Vec::new();
529 let mut start = 0;
530 let bytes = s.as_bytes();
531
532 for (i, ch) in s.char_indices() {
533 if ch == sep {
534 let mut backslashes = 0;
535 let mut j = i;
536 while j > 0 && bytes[j - 1] == b'\\' {
537 backslashes += 1;
538 j -= 1;
539 }
540 if backslashes % 2 == 0 {
541 parts.push(&s[start..i]);
542 start = i + ch.len_utf8();
543 }
544 }
545 }
546 parts.push(&s[start..]);
547 parts
548}
549
550#[cfg(test)]
551mod tests {
552 use super::*;
553
554 #[test]
558 fn an_empty_value_is_refused_at_the_boundary() {
559 let err = "{k=,after=sentinel}"
560 .parse::<Variables>()
561 .unwrap_err()
562 .to_string();
563 assert!(err.contains('k'), "error does not name the variable: {err}");
564 assert!(
565 err.contains("remove it"),
566 "error does not name a remedy: {err}"
567 );
568 assert!(
571 err.contains("presence"),
572 "error prescribes a fix without allowing that presence meant something: {err}"
573 );
574 }
575
576 #[test]
580 fn an_unbalanced_bracket_is_refused_while_a_balanced_one_is_not() {
581 assert!("{k=oops}extra}"
582 .parse::<Variables>()
583 .is_err());
584
585 let balanced: Variables = "{k=${some_var}}"
586 .parse()
587 .expect("a balanced ${...} is ordinary and must parse");
588 assert_eq!(balanced.get("k"), Some("${some_var}"));
589 }
590
591 #[test]
595 fn chosen_separator_leaves_commas_alone() {
596 let mut vars = Variables::new(VariablesType::Default);
597 vars.insert("codecs", "PCMA,PCMU,G729");
598 let vars = vars
599 .with_separator(':')
600 .unwrap();
601
602 assert_eq!(vars.to_string(), "{^^:codecs=PCMA,PCMU,G729}");
603 assert_eq!(vars.separator(), Some(':'));
604 }
605
606 #[test]
607 fn separator_that_cannot_delimit_the_block_is_refused() {
608 let vars = Variables::new(VariablesType::Channel);
609 for sep in ['[', ']', '=', '^'] {
610 assert!(
611 vars.clone()
612 .with_separator(sep)
613 .is_err(),
614 "accepted {sep:?}"
615 );
616 }
617 }
618
619 #[test]
623 fn the_parser_refuses_every_separator_the_builder_does() {
624 for sep in ['[', ']', '=', '^'] {
625 assert!(
626 format!("[^^{sep}a=1{sep}b=2]")
627 .parse::<Variables>()
628 .is_err(),
629 "parser accepted {sep:?}"
630 );
631 }
632 }
633
634 #[test]
637 fn separator_already_present_in_a_value_is_refused() {
638 let mut vars = Variables::new(VariablesType::Default);
639 vars.insert("uri", "sip:bob@example.com");
640 let err = vars
641 .with_separator(':')
642 .unwrap_err()
643 .to_string();
644 assert!(
645 err.contains("uri"),
646 "error does not name the variable: {err}"
647 );
648 }
649
650 #[test]
655 fn escaping_pins_the_measured_wire_forms() {
656 let cases = [
657 (DialStringCarrier::Dialplan, "it's", r"it\\'s"),
658 (DialStringCarrier::EslApi, "it's", r"it\\\'s"),
659 (DialStringCarrier::Dialplan, r"a\nb", r"a\\\\\\\\nb"),
660 (DialStringCarrier::EslApi, r"a\nb", r"a\\\\\\\\nb"),
661 (DialStringCarrier::Dialplan, "a,b", r"a\,b"),
662 (DialStringCarrier::EslApi, "a,b", r"a\,b"),
663 ];
664 for (carrier, value, want) in cases {
665 assert_eq!(
666 escape_value(value, carrier, true),
667 want,
668 "{value:?} for {carrier:?}"
669 );
670 }
671 }
672
673 #[test]
677 fn a_chosen_separator_changes_only_the_comma() {
678 let cases = [
679 (DialStringCarrier::Dialplan, "it's", r"it\\'s"),
680 (DialStringCarrier::EslApi, "it's", r"it\\\'s"),
681 (DialStringCarrier::Dialplan, r"a\nb", r"a\\\\\\\\nb"),
682 (DialStringCarrier::EslApi, r"a\nb", r"a\\\\\\\\nb"),
683 (DialStringCarrier::Dialplan, "a,b", "a,b"),
684 (DialStringCarrier::EslApi, "a,b", "a,b"),
685 ];
686 for (carrier, value, want) in cases {
687 assert_eq!(
688 escape_value(value, carrier, false),
689 want,
690 "{value:?} for {carrier:?}"
691 );
692 }
693 }
694
695 #[test]
698 fn display_defaults_to_the_api_carrier() {
699 let mut vars = Variables::new(VariablesType::Default);
700 vars.insert("k", "it's");
701 assert_eq!(vars.to_string(), r"{k=it\\\'s}");
702 assert_eq!(
703 vars.display_for(DialStringCarrier::Dialplan)
704 .to_string(),
705 r"{k=it\\'s}"
706 );
707 }
708
709 #[test]
710 fn round_trips_at_either_carrier() {
711 for carrier in [DialStringCarrier::EslApi, DialStringCarrier::Dialplan] {
712 for value in ["it's", r"a\,b", r"C:\path", "a,b", "with space"] {
713 let mut vars = Variables::new(VariablesType::Default);
714 vars.insert("k", value);
715 vars.insert("after", "sentinel");
716 let rendered = vars
717 .display_for(carrier)
718 .to_string();
719
720 let back = Variables::parse_for(&rendered, carrier).unwrap_or_else(|e| {
721 panic!("{value:?} for {carrier:?} rendered {rendered}: {e}")
722 });
723 assert_eq!(back.get("k"), Some(value), "rendered {rendered}");
724 assert_eq!(back.get("after"), Some("sentinel"), "rendered {rendered}");
725 }
726 }
727 }
728
729 #[test]
733 fn separated_block_round_trips_at_either_carrier() {
734 for carrier in [DialStringCarrier::EslApi, DialStringCarrier::Dialplan] {
735 for value in ["it's", "a,b", r"C:\path", r"a\nb", "with space"] {
736 let mut vars = Variables::new(VariablesType::Default);
737 vars.insert("k", value);
738 vars.insert("after", "sentinel");
739 let vars = vars
740 .with_separator('~')
741 .expect("'~' appears in none of these values");
742 let rendered = vars
743 .display_for(carrier)
744 .to_string();
745
746 let back = Variables::parse_for(&rendered, carrier).unwrap_or_else(|e| {
747 panic!("{value:?} for {carrier:?} rendered {rendered}: {e}")
748 });
749 assert_eq!(back.get("k"), Some(value), "rendered {rendered}");
750 assert_eq!(back.get("after"), Some("sentinel"), "rendered {rendered}");
751 }
752 }
753 }
754
755 #[test]
760 fn value_with_backslash_round_trips() {
761 for value in [r"a\,b", r"C:\path", r"trailing\", r"\\", r"a\nb"] {
762 let mut vars = Variables::new(VariablesType::Default);
763 vars.insert("k", value);
764 vars.insert("after", "sentinel");
765 let rendered = vars.to_string();
766
767 let back: Variables = rendered
768 .parse()
769 .unwrap_or_else(|e| {
770 panic!("{value:?} rendered {rendered} and failed to parse: {e}")
771 });
772 assert_eq!(back.get("k"), Some(value), "rendered {rendered}");
773 assert_eq!(
774 back.get("after"),
775 Some("sentinel"),
776 "value {value:?} ate the next variable: {rendered}"
777 );
778 }
779 }
780
781 #[test]
784 fn missing_equals_error_omits_the_fragment() {
785 let msg = "{origination_caller_id_number=15551234567,15550009999}"
786 .parse::<Variables>()
787 .unwrap_err()
788 .to_string();
789 assert!(
790 !msg.contains("15550009999"),
791 "error quoted its input: {msg}"
792 );
793 assert!(
794 msg.contains("variable 1"),
795 "error does not name the part: {msg}"
796 );
797 }
798
799 #[test]
800 fn unknown_delimiters_error_omits_the_block() {
801 let msg = "(origination_caller_id_number=15551234567)"
802 .parse::<Variables>()
803 .unwrap_err()
804 .to_string();
805 assert!(
806 !msg.contains("15551234567"),
807 "error quoted its input: {msg}"
808 );
809 }
810
811 #[test]
812 fn variables_standard_chars() {
813 let mut vars = Variables::new(VariablesType::Default);
814 vars.insert("test_key", "this_value");
815 vars.insert("second", "2");
816 assert_eq!(vars.to_string(), "{test_key=this_value,second=2}");
817 }
818
819 #[test]
820 fn variables_comma_escaped() {
821 let mut vars = Variables::new(VariablesType::Default);
822 vars.insert("test_key", "this,is,a,value");
823 let result = vars.to_string();
824 assert!(result.contains("\\,"));
825 }
826
827 #[test]
828 fn variables_spaces_quoted() {
829 let mut vars = Variables::new(VariablesType::Default);
830 vars.insert("test_key", "this is a value");
831 let result = vars.to_string();
832 assert_eq!(
833 result
834 .matches('\'')
835 .count(),
836 2
837 );
838 }
839
840 #[test]
841 fn variables_single_quote_escaped() {
842 let mut vars = Variables::new(VariablesType::Default);
843 vars.insert("test_key", "let's_this_be_a_value");
844 let result = vars.to_string();
845 assert!(result.contains("\\'"));
846 }
847
848 #[test]
849 fn variables_enterprise_delimiters() {
850 let mut vars = Variables::new(VariablesType::Enterprise);
851 vars.insert("k", "v");
852 let result = vars.to_string();
853 assert!(result.starts_with('<'));
854 assert!(result.ends_with('>'));
855 }
856
857 #[test]
858 fn variables_channel_delimiters() {
859 let mut vars = Variables::new(VariablesType::Channel);
860 vars.insert("k", "v");
861 let result = vars.to_string();
862 assert!(result.starts_with('['));
863 assert!(result.ends_with(']'));
864 }
865
866 #[test]
867 fn variables_default_delimiters() {
868 let mut vars = Variables::new(VariablesType::Default);
869 vars.insert("k", "v");
870 let result = vars.to_string();
871 assert!(result.starts_with('{'));
872 assert!(result.ends_with('}'));
873 }
874
875 #[test]
876 fn variables_parse_round_trip() {
877 let mut vars = Variables::new(VariablesType::Default);
878 vars.insert("origination_caller_id_number", "9005551212");
879 vars.insert("sip_h_Call-Info", "<url>;meta=123,<uri>");
880 let s = vars.to_string();
881 let parsed: Variables = s
882 .parse()
883 .unwrap();
884 assert_eq!(
885 parsed.get("origination_caller_id_number"),
886 Some("9005551212")
887 );
888 assert_eq!(parsed.get("sip_h_Call-Info"), Some("<url>;meta=123,<uri>"));
889 }
890
891 #[test]
892 fn split_unescaped_basic() {
893 assert_eq!(split_unescaped("a,b,c", ','), vec!["a", "b", "c"]);
894 assert_eq!(split_unescaped("a~b~c", '~'), vec!["a", "b", "c"]);
895 }
896
897 #[test]
898 fn split_unescaped_escaped() {
899 assert_eq!(split_unescaped(r"a\,b,c", ','), vec![r"a\,b", "c"]);
900 }
901
902 #[test]
903 fn split_unescaped_double_backslash() {
904 assert_eq!(split_unescaped(r"a\\,b", ','), vec![r"a\\", "b"]);
906 }
907
908 #[test]
909 fn split_unescaped_triple_backslash() {
910 assert_eq!(split_unescaped(r"a\\\,b", ','), vec![r"a\\\,b"]);
912 }
913
914 #[test]
915 fn variables_caret_caret_separator() {
916 let vars: Variables =
917 "[^^:sip_invite_domain=pbx.example.com:presence_id=1211@pbx.example.com]"
918 .parse()
919 .unwrap();
920 assert_eq!(vars.scope(), VariablesType::Channel);
921 assert_eq!(vars.get("sip_invite_domain"), Some("pbx.example.com"));
922 assert_eq!(vars.get("presence_id"), Some("1211@pbx.example.com"));
923 }
924
925 #[test]
926 fn variables_caret_caret_display_uses_canonical_comma() {
927 let vars: Variables = "[^^:a=1:b=2]"
928 .parse()
929 .unwrap();
930 assert_eq!(vars.to_string(), "[a=1,b=2]");
931 }
932
933 #[test]
934 fn variables_caret_caret_default_scope() {
935 let vars: Variables = "{^^|x=1|y=2}"
936 .parse()
937 .unwrap();
938 assert_eq!(vars.scope(), VariablesType::Default);
939 assert_eq!(vars.get("x"), Some("1"));
940 assert_eq!(vars.get("y"), Some("2"));
941 }
942
943 #[test]
944 fn variables_caret_caret_enterprise_scope() {
945 let vars: Variables = "<^^;a=1;b=2>"
946 .parse()
947 .unwrap();
948 assert_eq!(vars.scope(), VariablesType::Enterprise);
949 assert_eq!(vars.get("a"), Some("1"));
950 }
951
952 #[test]
956 fn variables_caret_caret_keeps_an_escaped_comma_literal() {
957 let vars: Variables = r"[^^:key=val\,ue:other=x]"
958 .parse()
959 .unwrap();
960 assert_eq!(vars.get("key"), Some(r"val\,ue"));
961 }
962
963 #[test]
964 fn variables_caret_caret_values_with_commas() {
965 let vars: Variables = "[^^|sip_h_X-Call-Info=<urn:foo>;purpose=bar,<urn:baz>|other=val]"
966 .parse()
967 .unwrap();
968 assert_eq!(
969 vars.get("sip_h_X-Call-Info"),
970 Some("<urn:foo>;purpose=bar,<urn:baz>")
971 );
972 assert_eq!(vars.get("other"), Some("val"));
973 }
974
975 #[test]
976 fn variables_caret_caret_empty_vars() {
977 let vars: Variables = "[^^:]"
978 .parse()
979 .unwrap();
980 assert!(vars.is_empty());
981 assert_eq!(vars.scope(), VariablesType::Channel);
982 }
983
984 #[test]
985 fn variables_caret_caret_missing_separator() {
986 assert!("[^^]"
987 .parse::<Variables>()
988 .is_err());
989 }
990
991 #[test]
992 fn variables_caret_caret_closing_bracket_as_sep() {
993 assert!("[^^]]"
994 .parse::<Variables>()
995 .is_err());
996 }
997
998 #[test]
999 fn variables_caret_caret_equals_as_sep() {
1000 assert!("[^^=a=1]"
1001 .parse::<Variables>()
1002 .is_err());
1003 }
1004
1005 #[test]
1006 fn variables_from_str_empty_block() {
1007 let result = "{}".parse::<Variables>();
1008 assert!(
1009 result.is_ok(),
1010 "empty variable block should parse successfully"
1011 );
1012 let vars = result.unwrap();
1013 assert!(
1014 vars.is_empty(),
1015 "parsed empty block should have no variables"
1016 );
1017 }
1018
1019 #[test]
1020 fn variables_from_str_empty_channel_block() {
1021 let result = "[]".parse::<Variables>();
1022 assert!(result.is_ok());
1023 let vars = result.unwrap();
1024 assert!(vars.is_empty());
1025 assert_eq!(vars.scope(), VariablesType::Channel);
1026 }
1027
1028 #[test]
1029 fn variables_from_str_empty_enterprise_block() {
1030 let result = "<>".parse::<Variables>();
1031 assert!(result.is_ok());
1032 let vars = result.unwrap();
1033 assert!(vars.is_empty());
1034 assert_eq!(vars.scope(), VariablesType::Enterprise);
1035 }
1036
1037 #[test]
1038 fn serde_variables_type() {
1039 let json = serde_json::to_string(&VariablesType::Enterprise).unwrap();
1040 assert_eq!(json, "\"enterprise\"");
1041 let parsed: VariablesType = serde_json::from_str(&json).unwrap();
1042 assert_eq!(parsed, VariablesType::Enterprise);
1043 }
1044
1045 #[test]
1046 fn serde_variables_flat_default() {
1047 let mut vars = Variables::new(VariablesType::Default);
1048 vars.insert("key1", "val1");
1049 vars.insert("key2", "val2");
1050 let json = serde_json::to_string(&vars).unwrap();
1051 let parsed: Variables = serde_json::from_str(&json).unwrap();
1052 assert_eq!(parsed.scope(), VariablesType::Default);
1053 assert_eq!(parsed.get("key1"), Some("val1"));
1054 assert_eq!(parsed.get("key2"), Some("val2"));
1055 }
1056
1057 #[test]
1058 fn serde_variables_scoped_enterprise() {
1059 let mut vars = Variables::new(VariablesType::Enterprise);
1060 vars.insert("key1", "val1");
1061 let json = serde_json::to_string(&vars).unwrap();
1062 assert!(json.contains("\"enterprise\""));
1063 let parsed: Variables = serde_json::from_str(&json).unwrap();
1064 assert_eq!(parsed.scope(), VariablesType::Enterprise);
1065 assert_eq!(parsed.get("key1"), Some("val1"));
1066 }
1067
1068 #[test]
1069 fn serde_variables_flat_map_deserializes_as_default() {
1070 let json = r#"{"key1":"val1","key2":"val2"}"#;
1071 let vars: Variables = serde_json::from_str(json).unwrap();
1072 assert_eq!(vars.scope(), VariablesType::Default);
1073 assert_eq!(vars.get("key1"), Some("val1"));
1074 assert_eq!(vars.get("key2"), Some("val2"));
1075 }
1076
1077 #[test]
1078 fn serde_variables_scoped_deserializes() {
1079 let json = r#"{"scope":"channel","vars":{"k":"v"}}"#;
1080 let vars: Variables = serde_json::from_str(json).unwrap();
1081 assert_eq!(vars.scope(), VariablesType::Channel);
1082 assert_eq!(vars.get("k"), Some("v"));
1083 }
1084
1085 #[test]
1088 fn serde_variables_separator_round_trips() {
1089 let mut vars = Variables::new(VariablesType::Default);
1090 vars.insert("codecs", "PCMA,PCMU");
1091 let vars = vars
1092 .with_separator('|')
1093 .unwrap();
1094
1095 let json = serde_json::to_string(&vars).unwrap();
1096 let parsed: Variables = serde_json::from_str(&json).unwrap();
1097 assert_eq!(parsed.separator(), Some('|'));
1098 assert_eq!(parsed, vars);
1099 assert_eq!(parsed.to_string(), "{^^|codecs=PCMA,PCMU}");
1100 }
1101
1102 #[test]
1105 fn serde_variables_default_separator_is_absent() {
1106 let mut flat = Variables::new(VariablesType::Default);
1107 flat.insert("k", "v");
1108 assert_eq!(serde_json::to_string(&flat).unwrap(), r#"{"k":"v"}"#);
1109
1110 let mut scoped = Variables::new(VariablesType::Channel);
1111 scoped.insert("k", "v");
1112 assert_eq!(
1113 serde_json::to_string(&scoped).unwrap(),
1114 r#"{"scope":"channel","vars":{"k":"v"}}"#
1115 );
1116 }
1117
1118 #[test]
1119 fn serde_variables_separator_deserializes_from_config() {
1120 let json = r#"{"scope":"channel","vars":{"codecs":"PCMA,PCMU"},"separator":"|"}"#;
1121 let vars: Variables = serde_json::from_str(json).unwrap();
1122 assert_eq!(vars.scope(), VariablesType::Channel);
1123 assert_eq!(vars.separator(), Some('|'));
1124 assert_eq!(vars.to_string(), "[^^|codecs=PCMA,PCMU]");
1125 }
1126
1127 #[test]
1130 fn serde_variables_unusable_separator_is_refused() {
1131 let undelimitable = r#"{"scope":"default","vars":{"k":"v"},"separator":"="}"#;
1132 assert!(serde_json::from_str::<Variables>(undelimitable).is_err());
1133
1134 let in_a_value =
1135 r#"{"scope":"default","vars":{"uri":"sip:bob@example.com"},"separator":":"}"#;
1136 let err = serde_json::from_str::<Variables>(in_a_value)
1137 .unwrap_err()
1138 .to_string();
1139 assert!(
1140 err.contains("uri"),
1141 "error does not name the variable: {err}"
1142 );
1143 }
1144}