Skip to main content

freeswitch_types/commands/
variables.rs

1//! Channel variable scope and ordered key-value storage for originate commands.
2
3use indexmap::IndexMap;
4use std::fmt;
5use std::str::FromStr;
6
7use super::originate::OriginateError;
8
9/// Scope for channel variables in an originate command.
10///
11/// - `Enterprise` (`<>`) -- applies across all threads (`:_:` separated)
12/// - `Default` (`{}`) -- applies to all channels in this originate
13/// - `Channel` (`[]`) -- applies only to one specific channel
14#[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    /// `<>` scope -- applies across all `:_:` separated threads.
20    Enterprise,
21    /// `{}` scope -- applies to all channels in this originate.
22    Default,
23    /// `[]` scope -- applies to one specific channel.
24    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/// Ordered set of channel variables with FreeSWITCH escaping.
38///
39/// Backslashes are doubled, commas are escaped with `\,`, single quotes with
40/// `\'`, and values with spaces are wrapped in single quotes. This form
41/// round-trips through [`FromStr`]; what the switch itself decodes depends on
42/// which command carries the block, and is documented in
43/// `docs/dial-string-format.md`.
44///
45/// # Serde format
46///
47/// [`Default`](VariablesType::Default) scope with the comma separator
48/// serializes as a flat JSON map: `{"key": "value", ...}`. Anything else
49/// serializes as `{"scope": "enterprise", "vars": {"key": "value"}}`, carrying
50/// a `"separator"` field only when [`with_separator`](Variables::with_separator)
51/// chose one. Deserialization accepts both formats; a flat map implies
52/// `Default` scope and the comma. A `separator` that cannot delimit the block,
53/// or that a value already contains, is refused at load.
54#[derive(Debug, Clone, PartialEq, Eq)]
55pub struct Variables {
56    vars_type: VariablesType,
57    inner: IndexMap<String, String>,
58    /// Set by [`with_separator`](Variables::with_separator). Parsing a `^^`
59    /// block does not populate it: reading one back and writing it out
60    /// canonicalises to a comma, and the other form is asked for explicitly.
61    separator: Option<char>,
62}
63
64/// Which command carries a dial string, and therefore how deeply its variable
65/// values must be escaped.
66///
67/// FreeSWITCH escape-processes a bracket block a different number of times
68/// depending on the command it arrived on, and the correct escaping of a value
69/// containing a single quote differs between them with no form that satisfies
70/// both. Naming the carrier is the only way a renderer can be right.
71#[derive(Debug, Clone, Copy, PartialEq, Eq)]
72#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
73#[non_exhaustive]
74pub enum DialStringCarrier {
75    /// `api originate` / `bgapi originate`, whose argument list is split before
76    /// the block itself is parsed. The default for [`Display`](fmt::Display).
77    EslApi,
78    /// A dialplan application such as `bridge`, including via `sendmsg execute`,
79    /// which receives its argument whole.
80    Dialplan,
81}
82
83/// A literal backslash, whatever follows it.
84const BACKSLASH: &str = r"\\\\\\\\";
85/// A literal single quote, per carrier.
86const 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
98/// Reject a value the wire cannot carry, whatever escaping is applied to it.
99///
100/// Two shapes qualify. An empty value is discarded by the switch under every
101/// encoding, and the block's own parse reports nothing when it happens. A value
102/// carrying an unbalanced bracket ends the block early, because the switch finds
103/// the block's end by counting depth and does not honour escapes while doing so;
104/// a balanced pair such as `${var}` is fine and common.
105fn 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
141/// Reject a separator that cannot delimit the block it was chosen for.
142///
143/// Either bracket moves the end the switch counts its way to, `=` splits the
144/// pair instead, and `^` leaves the `^^` prefix reading as its own separator.
145fn 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
155/// Escape a value for the wire, escaping the comma only when `commas_separate`
156/// says it is this block's separator; a `^^` block separates on something else
157/// and refuses a value carrying it, so a comma there is ordinary text.
158fn escape_value(value: &str, carrier: DialStringCarrier, commas_separate: bool) -> String {
159    // The backslash goes first, or the ones the other rules introduce get
160    // escaped in turn.
161    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
176/// Inverts [`escape_value`], undoing each substitution in the reverse order it
177/// was applied so an escape introduced by a later rule is not read as input to
178/// an earlier one.
179fn 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    /// Create an empty variable set with the given scope.
196    pub fn new(vars_type: VariablesType) -> Self {
197        Self {
198            vars_type,
199            inner: IndexMap::new(),
200            separator: None,
201        }
202    }
203
204    /// Create from an existing set of key-value pairs.
205    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    /// Separate the pairs with `sep` instead of a comma, emitting the block in
220    /// FreeSWITCH's `^^<sep>` form.
221    ///
222    /// A value expanded from `${...}` in a dialplan is substituted before the
223    /// block is parsed, so no escaping can be inserted into it; choosing a
224    /// separator none of the values contain is the only way such a value can
225    /// carry a comma. The separator is given rather than derived, so a block
226    /// renders the same way whatever its values happen to be that call.
227    ///
228    /// Fails if `sep` cannot delimit this block, or if a value already present
229    /// contains it. A value inserted afterwards is not checked: one carrying
230    /// `sep` splits into a pair nobody wrote, silently, until
231    /// [`insert`](Self::insert) becomes fallible (`docs/next-major.md`).
232    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    /// The `^^` separator this block renders with, if one was chosen.
248    pub fn separator(&self) -> Option<char> {
249        self.separator
250    }
251
252    /// Insert or overwrite a variable.
253    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    /// Remove a variable by name, returning its value if it existed.
259    pub fn remove(&mut self, key: &str) -> Option<String> {
260        self.inner
261            .shift_remove(key)
262    }
263
264    /// Look up a variable by name.
265    pub fn get(&self, key: &str) -> Option<&str> {
266        self.inner
267            .get(key)
268            .map(|s| s.as_str())
269    }
270
271    /// Whether the set contains no variables.
272    pub fn is_empty(&self) -> bool {
273        self.inner
274            .is_empty()
275    }
276
277    /// Number of variables.
278    pub fn len(&self) -> usize {
279        self.inner
280            .len()
281    }
282
283    /// Variable scope (Enterprise, Default, or Channel).
284    pub fn scope(&self) -> VariablesType {
285        self.vars_type
286    }
287
288    /// Change the variable scope.
289    pub fn set_scope(&mut self, scope: VariablesType) {
290        self.vars_type = scope;
291    }
292
293    /// Iterate over key-value pairs in insertion order.
294    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    /// Mutable iterator over key-value pairs in insertion order.
301    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    /// Mutable iterator over values in insertion order.
308    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        // A config naming a value the wire cannot carry fails at load rather
365        // than on the call it was loaded for.
366        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/// Renders a [`Variables`] for one carrier. Returned by
384/// [`Variables::display_for`].
385#[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    /// Render for a named carrier, rather than the [`DialStringCarrier::EslApi`]
400    /// default that [`Display`](fmt::Display) uses.
401    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        // A chosen separator carries the values that a comma would have needed
421        // escaping for, so only the comma form escapes them.
422        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    /// Parse a block written for a named carrier, mirroring
459    /// [`display_for`](Self::display_for). [`FromStr`] uses the same
460    /// [`DialStringCarrier::EslApi`] default as [`Display`](fmt::Display), so
461    /// the two round-trip.
462    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
522/// Split on separators that are not escaped by a backslash.
523///
524/// A separator preceded by an odd number of backslashes is escaped (e.g. `\,`).
525/// One preceded by an even number is a real split point (e.g. `\\,` is an
526/// escaped backslash followed by the delimiter).
527fn 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    /// Measured on a live switch: an empty value is discarded under every
555    /// encoding, silently, so nothing can be emitted for one and the refusal
556    /// has to name a remedy the caller could not guess.
557    #[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        // Prescribing removal alone is wrong for a caller whose *presence* test
569        // is the signal: dropping the key silently reverses that decision.
570        assert!(
571            err.contains("presence"),
572            "error prescribes a fix without allowing that presence meant something: {err}"
573        );
574    }
575
576    /// The switch finds a block's end by counting bracket depth and honours no
577    /// escape while doing so, so a value closing a bracket it never opened ends
578    /// the block early and the rest becomes dial-string text.
579    #[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    /// A chosen separator is what carries a comma through a value that was
592    /// expanded before the block was parsed, so the comma must survive as
593    /// ordinary text rather than picking up an escape.
594    #[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    /// The parser has to refuse what the builder refuses, `^` included:
620    /// accepting a block no render of this crate can reproduce hands the caller
621    /// a value that changes when it is written back out.
622    #[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    /// Refusing here is the whole point: a value carrying the separator would
635    /// split into a pair that was never written, and the switch reports nothing.
636    #[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    /// Measured against a live switch, with two awkward values in one block —
651    /// a block carrying only one is more forgiving and hides the failure.
652    /// Sweeping the backslash count, each carrier succeeds at counts the other
653    /// fails, so these forms are the wire contract and not a preference.
654    #[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    /// A `^^` block reaches the switch's tokenizer as often as the comma form,
674    /// so every rule above still holds there; only the comma stops being the
675    /// separator and so stops being escaped.
676    #[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    /// The crate exists to drive `api originate`, so a block rendered with no
696    /// carrier named is rendered for that one.
697    #[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    /// A chosen separator changes which character needs escaping, not whether
730    /// the block is escape-processed: the switch runs the same tokenizer over
731    /// it, so a value still comes back through the same undoing.
732    #[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    /// `split_unescaped_commas` reads a comma behind an even number of
756    /// backslashes as a real separator, so a value ending in a backslash has to
757    /// be written with its own backslash escaped or the writer contradicts the
758    /// reader and the block no longer parses.
759    #[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    /// A variable block holds dialled numbers and passthrough header values, so a
782    /// malformed part is reported by position.
783    #[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        // \\, = escaped backslash + comma delimiter
905        assert_eq!(split_unescaped(r"a\\,b", ','), vec![r"a\\", "b"]);
906    }
907
908    #[test]
909    fn split_unescaped_triple_backslash() {
910        // \\\, = escaped backslash + escaped comma (no split)
911        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    /// The switch consumes a backslash only before a quote, another backslash,
953    /// the separator in force, or a character it has an escape for -- so in a
954    /// block separated on something else, `\,` is two literal characters.
955    #[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    /// A block whose separator is dropped by a round trip renders as a comma
1086    /// block, which splits the very value the separator was chosen to carry.
1087    #[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    /// The default separator has no key, so a config written before the key
1103    /// existed serializes back byte-identical.
1104    #[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    /// The builder's two refusals have to hold at the config boundary too, or a
1128    /// YAML file produces a block the same crate would not build.
1129    #[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}