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)]
52pub struct Variables {
53 vars_type: VariablesType,
54 inner: IndexMap<String, String>,
55 separator: Option<char>,
59}
60
61#[derive(Debug, Clone, Copy, PartialEq, Eq)]
69#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
70#[non_exhaustive]
71pub enum DialStringCarrier {
72 EslApi,
75 Dialplan,
78}
79
80const BACKSLASH: &str = r"\\\\\\\\";
82const QUOTE_DIALPLAN: &str = r"\\'";
84const QUOTE_ESL_API: &str = r"\\\'";
85
86impl DialStringCarrier {
87 fn quote_escape(self) -> &'static str {
88 match self {
89 Self::EslApi => QUOTE_ESL_API,
90 Self::Dialplan => QUOTE_DIALPLAN,
91 }
92 }
93}
94
95fn check_representable(
103 key: &str,
104 value: &str,
105 vars_type: VariablesType,
106) -> Result<(), OriginateError> {
107 if value.is_empty() {
108 return Err(OriginateError::ParseError(format!(
109 "variable {key} has an empty value: the switch discards such a pair \
110 without logging it, so it cannot be told from an absent variable on \
111 the wire. Give it a value or remove it -- and if its presence was \
112 itself the signal, that signal needs a home outside the dial string"
113 )));
114 }
115
116 let (open, close) = vars_type.delimiters();
117 let mut depth = 0i32;
118 for ch in value.chars() {
119 if ch == open {
120 depth += 1;
121 } else if ch == close {
122 depth -= 1;
123 if depth < 0 {
124 return Err(OriginateError::ParseError(format!(
125 "variable {key} closes a '{open}' it never opened, ending the block early"
126 )));
127 }
128 }
129 }
130 if depth != 0 {
131 return Err(OriginateError::ParseError(format!(
132 "variable {key} opens a '{open}' it never closes, swallowing the block's end"
133 )));
134 }
135 Ok(())
136}
137
138fn escape_value_keeping_commas(value: &str, carrier: DialStringCarrier) -> String {
141 let escaped = value
142 .replace('\\', BACKSLASH)
143 .replace('\'', carrier.quote_escape());
144 if escaped.contains(' ') {
145 format!("'{}'", escaped)
146 } else {
147 escaped
148 }
149}
150
151pub(super) fn escape_value(value: &str, carrier: DialStringCarrier) -> String {
152 let escaped = value
155 .replace('\\', BACKSLASH)
156 .replace('\'', carrier.quote_escape())
157 .replace(',', "\\,");
158 if escaped.contains(' ') {
159 format!("'{}'", escaped)
160 } else {
161 escaped
162 }
163}
164
165fn unescape_value(value: &str, carrier: DialStringCarrier) -> String {
169 let s = value
170 .strip_prefix('\'')
171 .and_then(|s| s.strip_suffix('\''))
172 .unwrap_or(value);
173
174 s.replace("\\,", ",")
175 .replace(carrier.quote_escape(), "'")
176 .replace(BACKSLASH, "\\")
177}
178
179impl Variables {
180 pub fn new(vars_type: VariablesType) -> Self {
182 Self {
183 vars_type,
184 inner: IndexMap::new(),
185 separator: None,
186 }
187 }
188
189 pub fn with_vars(
191 vars_type: VariablesType,
192 vars: impl IntoIterator<Item = (impl Into<String>, impl Into<String>)>,
193 ) -> Self {
194 Self {
195 vars_type,
196 inner: vars
197 .into_iter()
198 .map(|(k, v)| (k.into(), v.into()))
199 .collect(),
200 separator: None,
201 }
202 }
203
204 pub fn with_separator(mut self, sep: char) -> Result<Self, OriginateError> {
216 let (_, close) = self
217 .vars_type
218 .delimiters();
219 if sep == close || sep == '=' || sep == '^' {
220 return Err(OriginateError::ParseError(format!(
221 "invalid ^^ separator: '{sep}'"
222 )));
223 }
224 if let Some((key, _)) = self
225 .inner
226 .iter()
227 .find(|(_, v)| v.contains(sep))
228 {
229 return Err(OriginateError::ParseError(format!(
230 "variable {key} contains the chosen '{sep}' separator"
231 )));
232 }
233 self.separator = Some(sep);
234 Ok(self)
235 }
236
237 pub fn separator(&self) -> Option<char> {
239 self.separator
240 }
241
242 pub fn insert(&mut self, key: impl Into<String>, value: impl Into<String>) {
244 self.inner
245 .insert(key.into(), value.into());
246 }
247
248 pub fn remove(&mut self, key: &str) -> Option<String> {
250 self.inner
251 .shift_remove(key)
252 }
253
254 pub fn get(&self, key: &str) -> Option<&str> {
256 self.inner
257 .get(key)
258 .map(|s| s.as_str())
259 }
260
261 pub fn is_empty(&self) -> bool {
263 self.inner
264 .is_empty()
265 }
266
267 pub fn len(&self) -> usize {
269 self.inner
270 .len()
271 }
272
273 pub fn scope(&self) -> VariablesType {
275 self.vars_type
276 }
277
278 pub fn set_scope(&mut self, scope: VariablesType) {
280 self.vars_type = scope;
281 }
282
283 pub fn iter(&self) -> impl Iterator<Item = (&str, &str)> {
285 self.inner
286 .iter()
287 .map(|(k, v)| (k.as_str(), v.as_str()))
288 }
289
290 pub fn iter_mut(&mut self) -> impl Iterator<Item = (&str, &mut String)> {
292 self.inner
293 .iter_mut()
294 .map(|(k, v)| (k.as_str(), v))
295 }
296
297 pub fn values_mut(&mut self) -> impl Iterator<Item = &mut String> {
299 self.inner
300 .values_mut()
301 }
302}
303
304#[cfg(feature = "serde")]
305impl serde::Serialize for Variables {
306 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
307 if self.vars_type == VariablesType::Default {
308 self.inner
309 .serialize(serializer)
310 } else {
311 use serde::ser::SerializeStruct;
312 let mut s = serializer.serialize_struct("Variables", 2)?;
313 s.serialize_field("scope", &self.vars_type)?;
314 s.serialize_field("vars", &self.inner)?;
315 s.end()
316 }
317 }
318}
319
320#[cfg(feature = "serde")]
321impl<'de> serde::Deserialize<'de> for Variables {
322 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
323 #[derive(serde::Deserialize)]
324 #[serde(untagged)]
325 enum VariablesRepr {
326 Scoped {
327 scope: VariablesType,
328 vars: IndexMap<String, String>,
329 },
330 Flat(IndexMap<String, String>),
331 }
332
333 let (vars_type, inner) = match VariablesRepr::deserialize(deserializer)? {
334 VariablesRepr::Scoped { scope, vars } => (scope, vars),
335 VariablesRepr::Flat(map) => (VariablesType::Default, map),
336 };
337 for (key, value) in &inner {
340 check_representable(key, value, vars_type).map_err(serde::de::Error::custom)?;
341 }
342 Ok(Self {
343 vars_type,
344 inner,
345 separator: None,
346 })
347 }
348}
349
350#[derive(Debug, Clone, Copy)]
353pub struct VariablesDisplay<'a> {
354 vars: &'a Variables,
355 carrier: DialStringCarrier,
356}
357
358impl fmt::Display for VariablesDisplay<'_> {
359 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
360 self.vars
361 .write_for(f, self.carrier)
362 }
363}
364
365impl Variables {
366 pub fn display_for(&self, carrier: DialStringCarrier) -> VariablesDisplay<'_> {
369 VariablesDisplay {
370 vars: self,
371 carrier,
372 }
373 }
374
375 pub(super) fn write_for(
376 &self,
377 f: &mut fmt::Formatter<'_>,
378 carrier: DialStringCarrier,
379 ) -> fmt::Result {
380 let (open, close) = self
381 .vars_type
382 .delimiters();
383 f.write_fmt(format_args!("{}", open))?;
384 if let Some(sep) = self.separator {
385 write!(f, "^^{sep}")?;
386 }
387 let sep = self
390 .separator
391 .unwrap_or(',');
392 for (i, (key, value)) in self
393 .inner
394 .iter()
395 .enumerate()
396 {
397 if i > 0 {
398 write!(f, "{sep}")?;
399 }
400 let value = match self.separator {
401 Some(_) => escape_value_keeping_commas(value, carrier),
402 None => escape_value(value, carrier),
403 };
404 write!(f, "{}={}", key, value)?;
405 }
406 f.write_fmt(format_args!("{}", close))
407 }
408}
409
410impl fmt::Display for Variables {
411 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
412 self.write_for(f, DialStringCarrier::EslApi)
413 }
414}
415
416impl FromStr for Variables {
417 type Err = OriginateError;
418
419 fn from_str(s: &str) -> Result<Self, Self::Err> {
420 Self::parse_for(s, DialStringCarrier::EslApi)
421 }
422}
423
424impl Variables {
425 pub fn parse_for(s: &str, carrier: DialStringCarrier) -> Result<Self, OriginateError> {
430 let s = s.trim();
431 if s.len() < 2 {
432 return Err(OriginateError::ParseError(
433 "variable block too short".into(),
434 ));
435 }
436
437 let (vars_type, inner_str) = match (s.as_bytes()[0], s.as_bytes()[s.len() - 1]) {
438 (b'{', b'}') => (VariablesType::Default, &s[1..s.len() - 1]),
439 (b'<', b'>') => (VariablesType::Enterprise, &s[1..s.len() - 1]),
440 (b'[', b']') => (VariablesType::Channel, &s[1..s.len() - 1]),
441 (open, close) => {
442 return Err(OriginateError::ParseError(format!(
443 "unknown variable delimiters: {:?}..{:?}",
444 open as char, close as char
445 )));
446 }
447 };
448
449 let mut inner = IndexMap::new();
450 if !inner_str.is_empty() {
451 if let Some(rest) = inner_str.strip_prefix("^^") {
452 let sep = rest
453 .chars()
454 .next()
455 .ok_or_else(|| {
456 OriginateError::ParseError("^^ without separator character".into())
457 })?;
458 let (_, close) = vars_type.delimiters();
459 if sep == close || sep == '=' {
460 return Err(OriginateError::ParseError(format!(
461 "invalid ^^ separator: '{sep}'"
462 )));
463 }
464 let var_str = &rest[sep.len_utf8()..];
465 if !var_str.is_empty() {
466 for (i, part) in var_str
467 .split(sep)
468 .enumerate()
469 {
470 let (key, value) = part
471 .split_once('=')
472 .ok_or_else(|| {
473 OriginateError::ParseError(format!("missing = in variable {i}"))
474 })?;
475 check_representable(key, value, vars_type)?;
476 inner.insert(key.to_string(), value.to_string());
477 }
478 }
479 } else {
480 for (i, part) in split_unescaped_commas(inner_str)
481 .into_iter()
482 .enumerate()
483 {
484 let (key, value) = part
485 .split_once('=')
486 .ok_or_else(|| {
487 OriginateError::ParseError(format!("missing = in variable {i}"))
488 })?;
489 let value = unescape_value(value, carrier);
490 check_representable(key, &value, vars_type)?;
491 inner.insert(key.to_string(), value);
492 }
493 }
494 }
495
496 Ok(Self {
497 vars_type,
498 inner,
499 separator: None,
500 })
501 }
502}
503
504pub(super) fn split_unescaped_commas(s: &str) -> Vec<&str> {
510 let mut parts = Vec::new();
511 let mut start = 0;
512 let bytes = s.as_bytes();
513
514 for i in 0..bytes.len() {
515 if bytes[i] == b',' {
516 let mut backslashes = 0;
517 let mut j = i;
518 while j > 0 && bytes[j - 1] == b'\\' {
519 backslashes += 1;
520 j -= 1;
521 }
522 if backslashes % 2 == 0 {
523 parts.push(&s[start..i]);
524 start = i + 1;
525 }
526 }
527 }
528 parts.push(&s[start..]);
529 parts
530}
531
532#[cfg(test)]
533mod tests {
534 use super::*;
535
536 #[test]
544 fn an_empty_value_is_refused_at_the_boundary() {
545 let err = "{k=,after=sentinel}"
546 .parse::<Variables>()
547 .unwrap_err()
548 .to_string();
549 assert!(err.contains('k'), "error does not name the variable: {err}");
550 assert!(
551 err.contains("remove it"),
552 "error does not name a remedy: {err}"
553 );
554 assert!(
557 err.contains("presence"),
558 "error prescribes a fix without allowing that presence meant something: {err}"
559 );
560 }
561
562 #[test]
566 fn an_unbalanced_bracket_is_refused_while_a_balanced_one_is_not() {
567 assert!("{k=oops}extra}"
568 .parse::<Variables>()
569 .is_err());
570
571 let balanced: Variables = "{k=${some_var}}"
572 .parse()
573 .expect("a balanced ${...} is ordinary and must parse");
574 assert_eq!(balanced.get("k"), Some("${some_var}"));
575 }
576
577 #[test]
581 fn chosen_separator_leaves_commas_alone() {
582 let mut vars = Variables::new(VariablesType::Default);
583 vars.insert("codecs", "PCMA,PCMU,G729");
584 let vars = vars
585 .with_separator(':')
586 .unwrap();
587
588 assert_eq!(vars.to_string(), "{^^:codecs=PCMA,PCMU,G729}");
589 assert_eq!(vars.separator(), Some(':'));
590 }
591
592 #[test]
593 fn separator_that_cannot_delimit_the_block_is_refused() {
594 let vars = Variables::new(VariablesType::Channel);
595 for sep in [']', '=', '^'] {
596 assert!(
597 vars.clone()
598 .with_separator(sep)
599 .is_err(),
600 "accepted {sep:?}"
601 );
602 }
603 }
604
605 #[test]
608 fn separator_already_present_in_a_value_is_refused() {
609 let mut vars = Variables::new(VariablesType::Default);
610 vars.insert("uri", "sip:bob@example.com");
611 let err = vars
612 .with_separator(':')
613 .unwrap_err()
614 .to_string();
615 assert!(
616 err.contains("uri"),
617 "error does not name the variable: {err}"
618 );
619 }
620
621 #[test]
626 fn escaping_pins_the_measured_wire_forms() {
627 let cases = [
628 (DialStringCarrier::Dialplan, "it's", r"it\\'s"),
629 (DialStringCarrier::EslApi, "it's", r"it\\\'s"),
630 (DialStringCarrier::Dialplan, r"a\nb", r"a\\\\\\\\nb"),
631 (DialStringCarrier::EslApi, r"a\nb", r"a\\\\\\\\nb"),
632 (DialStringCarrier::Dialplan, "a,b", r"a\,b"),
633 (DialStringCarrier::EslApi, "a,b", r"a\,b"),
634 ];
635 for (carrier, value, want) in cases {
636 assert_eq!(
637 escape_value(value, carrier),
638 want,
639 "{value:?} for {carrier:?}"
640 );
641 }
642 }
643
644 #[test]
647 fn display_defaults_to_the_api_carrier() {
648 let mut vars = Variables::new(VariablesType::Default);
649 vars.insert("k", "it's");
650 assert_eq!(vars.to_string(), r"{k=it\\\'s}");
651 assert_eq!(
652 vars.display_for(DialStringCarrier::Dialplan)
653 .to_string(),
654 r"{k=it\\'s}"
655 );
656 }
657
658 #[test]
659 fn round_trips_at_either_carrier() {
660 for carrier in [DialStringCarrier::EslApi, DialStringCarrier::Dialplan] {
661 for value in ["it's", r"a\,b", r"C:\path", "a,b", "with space"] {
662 let mut vars = Variables::new(VariablesType::Default);
663 vars.insert("k", value);
664 vars.insert("after", "sentinel");
665 let rendered = vars
666 .display_for(carrier)
667 .to_string();
668
669 let back = Variables::parse_for(&rendered, carrier).unwrap_or_else(|e| {
670 panic!("{value:?} for {carrier:?} rendered {rendered}: {e}")
671 });
672 assert_eq!(back.get("k"), Some(value), "rendered {rendered}");
673 assert_eq!(back.get("after"), Some("sentinel"), "rendered {rendered}");
674 }
675 }
676 }
677
678 #[test]
683 fn value_with_backslash_round_trips() {
684 for value in [r"a\,b", r"C:\path", r"trailing\", r"\\", r"a\nb"] {
685 let mut vars = Variables::new(VariablesType::Default);
686 vars.insert("k", value);
687 vars.insert("after", "sentinel");
688 let rendered = vars.to_string();
689
690 let back: Variables = rendered
691 .parse()
692 .unwrap_or_else(|e| {
693 panic!("{value:?} rendered {rendered} and failed to parse: {e}")
694 });
695 assert_eq!(back.get("k"), Some(value), "rendered {rendered}");
696 assert_eq!(
697 back.get("after"),
698 Some("sentinel"),
699 "value {value:?} ate the next variable: {rendered}"
700 );
701 }
702 }
703
704 #[test]
707 fn missing_equals_error_omits_the_fragment() {
708 let msg = "{origination_caller_id_number=15551234567,15550009999}"
709 .parse::<Variables>()
710 .unwrap_err()
711 .to_string();
712 assert!(
713 !msg.contains("15550009999"),
714 "error quoted its input: {msg}"
715 );
716 assert!(
717 msg.contains("variable 1"),
718 "error does not name the part: {msg}"
719 );
720 }
721
722 #[test]
723 fn unknown_delimiters_error_omits_the_block() {
724 let msg = "(origination_caller_id_number=15551234567)"
725 .parse::<Variables>()
726 .unwrap_err()
727 .to_string();
728 assert!(
729 !msg.contains("15551234567"),
730 "error quoted its input: {msg}"
731 );
732 }
733
734 #[test]
735 fn variables_standard_chars() {
736 let mut vars = Variables::new(VariablesType::Default);
737 vars.insert("test_key", "this_value");
738 let result = vars.to_string();
739 assert!(result.contains("test_key"));
740 assert!(result.contains("this_value"));
741 }
742
743 #[test]
744 fn variables_comma_escaped() {
745 let mut vars = Variables::new(VariablesType::Default);
746 vars.insert("test_key", "this,is,a,value");
747 let result = vars.to_string();
748 assert!(result.contains("\\,"));
749 }
750
751 #[test]
752 fn variables_spaces_quoted() {
753 let mut vars = Variables::new(VariablesType::Default);
754 vars.insert("test_key", "this is a value");
755 let result = vars.to_string();
756 assert_eq!(
757 result
758 .matches('\'')
759 .count(),
760 2
761 );
762 }
763
764 #[test]
765 fn variables_single_quote_escaped() {
766 let mut vars = Variables::new(VariablesType::Default);
767 vars.insert("test_key", "let's_this_be_a_value");
768 let result = vars.to_string();
769 assert!(result.contains("\\'"));
770 }
771
772 #[test]
773 fn variables_enterprise_delimiters() {
774 let mut vars = Variables::new(VariablesType::Enterprise);
775 vars.insert("k", "v");
776 let result = vars.to_string();
777 assert!(result.starts_with('<'));
778 assert!(result.ends_with('>'));
779 }
780
781 #[test]
782 fn variables_channel_delimiters() {
783 let mut vars = Variables::new(VariablesType::Channel);
784 vars.insert("k", "v");
785 let result = vars.to_string();
786 assert!(result.starts_with('['));
787 assert!(result.ends_with(']'));
788 }
789
790 #[test]
791 fn variables_default_delimiters() {
792 let mut vars = Variables::new(VariablesType::Default);
793 vars.insert("k", "v");
794 let result = vars.to_string();
795 assert!(result.starts_with('{'));
796 assert!(result.ends_with('}'));
797 }
798
799 #[test]
800 fn variables_parse_round_trip() {
801 let mut vars = Variables::new(VariablesType::Default);
802 vars.insert("origination_caller_id_number", "9005551212");
803 vars.insert("sip_h_Call-Info", "<url>;meta=123,<uri>");
804 let s = vars.to_string();
805 let parsed: Variables = s
806 .parse()
807 .unwrap();
808 assert_eq!(
809 parsed.get("origination_caller_id_number"),
810 Some("9005551212")
811 );
812 assert_eq!(parsed.get("sip_h_Call-Info"), Some("<url>;meta=123,<uri>"));
813 }
814
815 #[test]
816 fn split_unescaped_commas_basic() {
817 assert_eq!(split_unescaped_commas("a,b,c"), vec!["a", "b", "c"]);
818 }
819
820 #[test]
821 fn split_unescaped_commas_escaped() {
822 assert_eq!(split_unescaped_commas(r"a\,b,c"), vec![r"a\,b", "c"]);
823 }
824
825 #[test]
826 fn split_unescaped_commas_double_backslash() {
827 assert_eq!(split_unescaped_commas(r"a\\,b"), vec![r"a\\", "b"]);
829 }
830
831 #[test]
832 fn split_unescaped_commas_triple_backslash() {
833 assert_eq!(split_unescaped_commas(r"a\\\,b"), vec![r"a\\\,b"]);
835 }
836
837 #[test]
838 fn variables_caret_caret_separator() {
839 let vars: Variables =
840 "[^^:sip_invite_domain=pbx.example.com:presence_id=1211@pbx.example.com]"
841 .parse()
842 .unwrap();
843 assert_eq!(vars.scope(), VariablesType::Channel);
844 assert_eq!(vars.get("sip_invite_domain"), Some("pbx.example.com"));
845 assert_eq!(vars.get("presence_id"), Some("1211@pbx.example.com"));
846 }
847
848 #[test]
849 fn variables_caret_caret_display_uses_canonical_comma() {
850 let vars: Variables = "[^^:a=1:b=2]"
851 .parse()
852 .unwrap();
853 assert_eq!(vars.to_string(), "[a=1,b=2]");
854 }
855
856 #[test]
857 fn variables_caret_caret_default_scope() {
858 let vars: Variables = "{^^|x=1|y=2}"
859 .parse()
860 .unwrap();
861 assert_eq!(vars.scope(), VariablesType::Default);
862 assert_eq!(vars.get("x"), Some("1"));
863 assert_eq!(vars.get("y"), Some("2"));
864 }
865
866 #[test]
867 fn variables_caret_caret_enterprise_scope() {
868 let vars: Variables = "<^^;a=1;b=2>"
869 .parse()
870 .unwrap();
871 assert_eq!(vars.scope(), VariablesType::Enterprise);
872 assert_eq!(vars.get("a"), Some("1"));
873 }
874
875 #[test]
876 fn variables_caret_caret_no_unescape() {
877 let vars: Variables = r"[^^:key=val\,ue:other=x]"
878 .parse()
879 .unwrap();
880 assert_eq!(vars.get("key"), Some(r"val\,ue"));
881 }
882
883 #[test]
884 fn variables_caret_caret_values_with_commas() {
885 let vars: Variables = "[^^|sip_h_X-Call-Info=<urn:foo>;purpose=bar,<urn:baz>|other=val]"
886 .parse()
887 .unwrap();
888 assert_eq!(
889 vars.get("sip_h_X-Call-Info"),
890 Some("<urn:foo>;purpose=bar,<urn:baz>")
891 );
892 assert_eq!(vars.get("other"), Some("val"));
893 }
894
895 #[test]
896 fn variables_caret_caret_empty_vars() {
897 let vars: Variables = "[^^:]"
898 .parse()
899 .unwrap();
900 assert!(vars.is_empty());
901 assert_eq!(vars.scope(), VariablesType::Channel);
902 }
903
904 #[test]
905 fn variables_caret_caret_missing_separator() {
906 assert!("[^^]"
907 .parse::<Variables>()
908 .is_err());
909 }
910
911 #[test]
912 fn variables_caret_caret_closing_bracket_as_sep() {
913 assert!("[^^]]"
914 .parse::<Variables>()
915 .is_err());
916 }
917
918 #[test]
919 fn variables_caret_caret_equals_as_sep() {
920 assert!("[^^=a=1]"
921 .parse::<Variables>()
922 .is_err());
923 }
924
925 #[test]
926 fn variables_from_str_empty_block() {
927 let result = "{}".parse::<Variables>();
928 assert!(
929 result.is_ok(),
930 "empty variable block should parse successfully"
931 );
932 let vars = result.unwrap();
933 assert!(
934 vars.is_empty(),
935 "parsed empty block should have no variables"
936 );
937 }
938
939 #[test]
940 fn variables_from_str_empty_channel_block() {
941 let result = "[]".parse::<Variables>();
942 assert!(result.is_ok());
943 let vars = result.unwrap();
944 assert!(vars.is_empty());
945 assert_eq!(vars.scope(), VariablesType::Channel);
946 }
947
948 #[test]
949 fn variables_from_str_empty_enterprise_block() {
950 let result = "<>".parse::<Variables>();
951 assert!(result.is_ok());
952 let vars = result.unwrap();
953 assert!(vars.is_empty());
954 assert_eq!(vars.scope(), VariablesType::Enterprise);
955 }
956
957 #[test]
958 fn serde_variables_type() {
959 let json = serde_json::to_string(&VariablesType::Enterprise).unwrap();
960 assert_eq!(json, "\"enterprise\"");
961 let parsed: VariablesType = serde_json::from_str(&json).unwrap();
962 assert_eq!(parsed, VariablesType::Enterprise);
963 }
964
965 #[test]
966 fn serde_variables_flat_default() {
967 let mut vars = Variables::new(VariablesType::Default);
968 vars.insert("key1", "val1");
969 vars.insert("key2", "val2");
970 let json = serde_json::to_string(&vars).unwrap();
971 let parsed: Variables = serde_json::from_str(&json).unwrap();
972 assert_eq!(parsed.scope(), VariablesType::Default);
973 assert_eq!(parsed.get("key1"), Some("val1"));
974 assert_eq!(parsed.get("key2"), Some("val2"));
975 }
976
977 #[test]
978 fn serde_variables_scoped_enterprise() {
979 let mut vars = Variables::new(VariablesType::Enterprise);
980 vars.insert("key1", "val1");
981 let json = serde_json::to_string(&vars).unwrap();
982 assert!(json.contains("\"enterprise\""));
983 let parsed: Variables = serde_json::from_str(&json).unwrap();
984 assert_eq!(parsed.scope(), VariablesType::Enterprise);
985 assert_eq!(parsed.get("key1"), Some("val1"));
986 }
987
988 #[test]
989 fn serde_variables_flat_map_deserializes_as_default() {
990 let json = r#"{"key1":"val1","key2":"val2"}"#;
991 let vars: Variables = serde_json::from_str(json).unwrap();
992 assert_eq!(vars.scope(), VariablesType::Default);
993 assert_eq!(vars.get("key1"), Some("val1"));
994 assert_eq!(vars.get("key2"), Some("val2"));
995 }
996
997 #[test]
998 fn serde_variables_scoped_deserializes() {
999 let json = r#"{"scope":"channel","vars":{"k":"v"}}"#;
1000 let vars: Variables = serde_json::from_str(json).unwrap();
1001 assert_eq!(vars.scope(), VariablesType::Channel);
1002 assert_eq!(vars.get("k"), Some("v"));
1003 }
1004}