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 serializes as a flat JSON map:
48/// `{"key": "value", ...}`. Non-default scopes serialize as
49/// `{"scope": "Enterprise", "vars": {"key": "value"}}`.
50/// Deserialization accepts both formats; a flat map implies `Default` scope.
51#[derive(Debug, Clone, PartialEq, Eq)]
52pub struct Variables {
53    vars_type: VariablesType,
54    inner: IndexMap<String, String>,
55    /// Set by [`with_separator`](Variables::with_separator). Parsing a `^^`
56    /// block does not populate it: reading one back and writing it out
57    /// canonicalises to a comma, and the other form is asked for explicitly.
58    separator: Option<char>,
59}
60
61/// Which command carries a dial string, and therefore how deeply its variable
62/// values must be escaped.
63///
64/// FreeSWITCH escape-processes a bracket block a different number of times
65/// depending on the command it arrived on, and the correct escaping of a value
66/// containing a single quote differs between them with no form that satisfies
67/// both. Naming the carrier is the only way a renderer can be right.
68#[derive(Debug, Clone, Copy, PartialEq, Eq)]
69#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
70#[non_exhaustive]
71pub enum DialStringCarrier {
72    /// `api originate` / `bgapi originate`, whose argument list is split before
73    /// the block itself is parsed. The default for [`Display`](fmt::Display).
74    EslApi,
75    /// A dialplan application such as `bridge`, including via `sendmsg execute`,
76    /// which receives its argument whole.
77    Dialplan,
78}
79
80/// A literal backslash, whatever follows it.
81const BACKSLASH: &str = r"\\\\\\\\";
82/// A literal single quote, per carrier.
83const 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
95/// Reject a value the wire cannot carry, whatever escaping is applied to it.
96///
97/// Two shapes qualify. An empty value is discarded by the switch under every
98/// encoding, and the block's own parse reports nothing when it happens. A value
99/// carrying an unbalanced bracket ends the block early, because the switch finds
100/// the block's end by counting depth and does not honour escapes while doing so;
101/// a balanced pair such as `${var}` is fine and common.
102fn 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
138/// Reject a separator that cannot delimit the block it was chosen for.
139///
140/// Either bracket moves the end the switch counts its way to, `=` splits the
141/// pair instead, and `^` leaves the `^^` prefix reading as its own separator.
142fn check_separator(sep: char, vars_type: VariablesType) -> Result<(), OriginateError> {
143    let (open, close) = vars_type.delimiters();
144    if sep == open || sep == close || sep == '=' || sep == '^' {
145        return Err(OriginateError::ParseError(format!(
146            "invalid ^^ separator: '{sep}'"
147        )));
148    }
149    Ok(())
150}
151
152/// Escape a value for the wire, escaping the comma only when `commas_separate`
153/// says it is this block's separator; a `^^` block separates on something else
154/// and refuses a value carrying it, so a comma there is ordinary text.
155fn escape_value(value: &str, carrier: DialStringCarrier, commas_separate: bool) -> String {
156    // The backslash goes first, or the ones the other rules introduce get
157    // escaped in turn.
158    let escaped = value
159        .replace('\\', BACKSLASH)
160        .replace('\'', carrier.quote_escape());
161    let escaped = if commas_separate {
162        escaped.replace(',', "\\,")
163    } else {
164        escaped
165    };
166    if escaped.contains(' ') {
167        format!("'{}'", escaped)
168    } else {
169        escaped
170    }
171}
172
173/// Inverts [`escape_value`], undoing each substitution in the reverse order it
174/// was applied so an escape introduced by a later rule is not read as input to
175/// an earlier one.
176fn unescape_value(value: &str, carrier: DialStringCarrier, commas_separate: bool) -> String {
177    let s = value
178        .strip_prefix('\'')
179        .and_then(|s| s.strip_suffix('\''))
180        .unwrap_or(value);
181
182    let s = if commas_separate {
183        s.replace("\\,", ",")
184    } else {
185        s.to_string()
186    };
187    s.replace(carrier.quote_escape(), "'")
188        .replace(BACKSLASH, "\\")
189}
190
191impl Variables {
192    /// Create an empty variable set with the given scope.
193    pub fn new(vars_type: VariablesType) -> Self {
194        Self {
195            vars_type,
196            inner: IndexMap::new(),
197            separator: None,
198        }
199    }
200
201    /// Create from an existing set of key-value pairs.
202    pub fn with_vars(
203        vars_type: VariablesType,
204        vars: impl IntoIterator<Item = (impl Into<String>, impl Into<String>)>,
205    ) -> Self {
206        Self {
207            vars_type,
208            inner: vars
209                .into_iter()
210                .map(|(k, v)| (k.into(), v.into()))
211                .collect(),
212            separator: None,
213        }
214    }
215
216    /// Separate the pairs with `sep` instead of a comma, emitting the block in
217    /// FreeSWITCH's `^^<sep>` form.
218    ///
219    /// A value expanded from `${...}` in a dialplan is substituted before the
220    /// block is parsed, so no escaping can be inserted into it; choosing a
221    /// separator none of the values contain is the only way such a value can
222    /// carry a comma. The separator is given rather than derived, so a block
223    /// renders the same way whatever its values happen to be that call.
224    ///
225    /// Fails if `sep` cannot delimit this block, or if a value already present
226    /// contains it. Values inserted afterwards are not checked.
227    pub fn with_separator(mut self, sep: char) -> Result<Self, OriginateError> {
228        check_separator(sep, self.vars_type)?;
229        if let Some((key, _)) = self
230            .inner
231            .iter()
232            .find(|(_, v)| v.contains(sep))
233        {
234            return Err(OriginateError::ParseError(format!(
235                "variable {key} contains the chosen '{sep}' separator"
236            )));
237        }
238        self.separator = Some(sep);
239        Ok(self)
240    }
241
242    /// The `^^` separator this block renders with, if one was chosen.
243    pub fn separator(&self) -> Option<char> {
244        self.separator
245    }
246
247    /// Insert or overwrite a variable.
248    pub fn insert(&mut self, key: impl Into<String>, value: impl Into<String>) {
249        self.inner
250            .insert(key.into(), value.into());
251    }
252
253    /// Remove a variable by name, returning its value if it existed.
254    pub fn remove(&mut self, key: &str) -> Option<String> {
255        self.inner
256            .shift_remove(key)
257    }
258
259    /// Look up a variable by name.
260    pub fn get(&self, key: &str) -> Option<&str> {
261        self.inner
262            .get(key)
263            .map(|s| s.as_str())
264    }
265
266    /// Whether the set contains no variables.
267    pub fn is_empty(&self) -> bool {
268        self.inner
269            .is_empty()
270    }
271
272    /// Number of variables.
273    pub fn len(&self) -> usize {
274        self.inner
275            .len()
276    }
277
278    /// Variable scope (Enterprise, Default, or Channel).
279    pub fn scope(&self) -> VariablesType {
280        self.vars_type
281    }
282
283    /// Change the variable scope.
284    pub fn set_scope(&mut self, scope: VariablesType) {
285        self.vars_type = scope;
286    }
287
288    /// Iterate over key-value pairs in insertion order.
289    pub fn iter(&self) -> impl Iterator<Item = (&str, &str)> {
290        self.inner
291            .iter()
292            .map(|(k, v)| (k.as_str(), v.as_str()))
293    }
294
295    /// Mutable iterator over key-value pairs in insertion order.
296    pub fn iter_mut(&mut self) -> impl Iterator<Item = (&str, &mut String)> {
297        self.inner
298            .iter_mut()
299            .map(|(k, v)| (k.as_str(), v))
300    }
301
302    /// Mutable iterator over values in insertion order.
303    pub fn values_mut(&mut self) -> impl Iterator<Item = &mut String> {
304        self.inner
305            .values_mut()
306    }
307}
308
309#[cfg(feature = "serde")]
310impl serde::Serialize for Variables {
311    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
312        if self.vars_type == VariablesType::Default {
313            self.inner
314                .serialize(serializer)
315        } else {
316            use serde::ser::SerializeStruct;
317            let mut s = serializer.serialize_struct("Variables", 2)?;
318            s.serialize_field("scope", &self.vars_type)?;
319            s.serialize_field("vars", &self.inner)?;
320            s.end()
321        }
322    }
323}
324
325#[cfg(feature = "serde")]
326impl<'de> serde::Deserialize<'de> for Variables {
327    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
328        #[derive(serde::Deserialize)]
329        #[serde(untagged)]
330        enum VariablesRepr {
331            Scoped {
332                scope: VariablesType,
333                vars: IndexMap<String, String>,
334            },
335            Flat(IndexMap<String, String>),
336        }
337
338        let (vars_type, inner) = match VariablesRepr::deserialize(deserializer)? {
339            VariablesRepr::Scoped { scope, vars } => (scope, vars),
340            VariablesRepr::Flat(map) => (VariablesType::Default, map),
341        };
342        // A config naming a value the wire cannot carry fails at load rather
343        // than on the call it was loaded for.
344        for (key, value) in &inner {
345            check_representable(key, value, vars_type).map_err(serde::de::Error::custom)?;
346        }
347        Ok(Self {
348            vars_type,
349            inner,
350            separator: None,
351        })
352    }
353}
354
355/// Renders a [`Variables`] for one carrier. Returned by
356/// [`Variables::display_for`].
357#[derive(Debug, Clone, Copy)]
358pub struct VariablesDisplay<'a> {
359    vars: &'a Variables,
360    carrier: DialStringCarrier,
361}
362
363impl fmt::Display for VariablesDisplay<'_> {
364    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
365        self.vars
366            .write_for(f, self.carrier)
367    }
368}
369
370impl Variables {
371    /// Render for a named carrier, rather than the [`DialStringCarrier::EslApi`]
372    /// default that [`Display`](fmt::Display) uses.
373    pub fn display_for(&self, carrier: DialStringCarrier) -> VariablesDisplay<'_> {
374        VariablesDisplay {
375            vars: self,
376            carrier,
377        }
378    }
379
380    pub(super) fn write_for(
381        &self,
382        f: &mut fmt::Formatter<'_>,
383        carrier: DialStringCarrier,
384    ) -> fmt::Result {
385        let (open, close) = self
386            .vars_type
387            .delimiters();
388        f.write_fmt(format_args!("{}", open))?;
389        if let Some(sep) = self.separator {
390            write!(f, "^^{sep}")?;
391        }
392        // A chosen separator carries the values that a comma would have needed
393        // escaping for, so only the comma form escapes them.
394        let commas_separate = self
395            .separator
396            .is_none();
397        let sep = self
398            .separator
399            .unwrap_or(',');
400        for (i, (key, value)) in self
401            .inner
402            .iter()
403            .enumerate()
404        {
405            if i > 0 {
406                write!(f, "{sep}")?;
407            }
408            let value = escape_value(value, carrier, commas_separate);
409            write!(f, "{}={}", key, value)?;
410        }
411        f.write_fmt(format_args!("{}", close))
412    }
413}
414
415impl fmt::Display for Variables {
416    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
417        self.write_for(f, DialStringCarrier::EslApi)
418    }
419}
420
421impl FromStr for Variables {
422    type Err = OriginateError;
423
424    fn from_str(s: &str) -> Result<Self, Self::Err> {
425        Self::parse_for(s, DialStringCarrier::EslApi)
426    }
427}
428
429impl Variables {
430    /// Parse a block written for a named carrier, mirroring
431    /// [`display_for`](Self::display_for). [`FromStr`] uses the same
432    /// [`DialStringCarrier::EslApi`] default as [`Display`](fmt::Display), so
433    /// the two round-trip.
434    pub fn parse_for(s: &str, carrier: DialStringCarrier) -> Result<Self, OriginateError> {
435        let s = s.trim();
436        if s.len() < 2 {
437            return Err(OriginateError::ParseError(
438                "variable block too short".into(),
439            ));
440        }
441
442        let (vars_type, inner_str) = match (s.as_bytes()[0], s.as_bytes()[s.len() - 1]) {
443            (b'{', b'}') => (VariablesType::Default, &s[1..s.len() - 1]),
444            (b'<', b'>') => (VariablesType::Enterprise, &s[1..s.len() - 1]),
445            (b'[', b']') => (VariablesType::Channel, &s[1..s.len() - 1]),
446            (open, close) => {
447                return Err(OriginateError::ParseError(format!(
448                    "unknown variable delimiters: {:?}..{:?}",
449                    open as char, close as char
450                )));
451            }
452        };
453
454        let (sep, var_str) = match inner_str.strip_prefix("^^") {
455            Some(rest) => {
456                let sep = rest
457                    .chars()
458                    .next()
459                    .ok_or_else(|| {
460                        OriginateError::ParseError("^^ without separator character".into())
461                    })?;
462                check_separator(sep, vars_type)?;
463                (sep, &rest[sep.len_utf8()..])
464            }
465            None => (',', inner_str),
466        };
467        let commas_separate = sep == ',';
468
469        let mut inner = IndexMap::new();
470        if !var_str.is_empty() {
471            for (i, part) in split_unescaped(var_str, sep)
472                .into_iter()
473                .enumerate()
474            {
475                let (key, value) = part
476                    .split_once('=')
477                    .ok_or_else(|| {
478                        OriginateError::ParseError(format!("missing = in variable {i}"))
479                    })?;
480                let value = unescape_value(value, carrier, commas_separate);
481                check_representable(key, &value, vars_type)?;
482                inner.insert(key.to_string(), value);
483            }
484        }
485
486        Ok(Self {
487            vars_type,
488            inner,
489            separator: None,
490        })
491    }
492}
493
494/// Split on separators that are not escaped by a backslash.
495///
496/// A separator preceded by an odd number of backslashes is escaped (e.g. `\,`).
497/// One preceded by an even number is a real split point (e.g. `\\,` is an
498/// escaped backslash followed by the delimiter).
499fn split_unescaped(s: &str, sep: char) -> Vec<&str> {
500    let mut parts = Vec::new();
501    let mut start = 0;
502    let bytes = s.as_bytes();
503
504    for (i, ch) in s.char_indices() {
505        if ch == sep {
506            let mut backslashes = 0;
507            let mut j = i;
508            while j > 0 && bytes[j - 1] == b'\\' {
509                backslashes += 1;
510                j -= 1;
511            }
512            if backslashes % 2 == 0 {
513                parts.push(&s[start..i]);
514                start = i + ch.len_utf8();
515            }
516        }
517    }
518    parts.push(&s[start..]);
519    parts
520}
521
522#[cfg(test)]
523mod tests {
524    use super::*;
525
526    /// Measured on a live switch: bare, quoted, escaped-quoted and
527    /// backslash-quoted empty values are all discarded, on both carriers, with
528    /// no diagnostic from the switch at any level. Nothing can be emitted for
529    /// one, so it is refused where refusing is possible.
530    /// A config that loaded yesterday and does not today has to say what to do
531    /// about it: the remedy is dropping the key, which is not guessable from
532    /// being told the value is unrepresentable.
533    #[test]
534    fn an_empty_value_is_refused_at_the_boundary() {
535        let err = "{k=,after=sentinel}"
536            .parse::<Variables>()
537            .unwrap_err()
538            .to_string();
539        assert!(err.contains('k'), "error does not name the variable: {err}");
540        assert!(
541            err.contains("remove it"),
542            "error does not name a remedy: {err}"
543        );
544        // Prescribing removal alone is wrong for a caller whose *presence* test
545        // is the signal: dropping the key silently reverses that decision.
546        assert!(
547            err.contains("presence"),
548            "error prescribes a fix without allowing that presence meant something: {err}"
549        );
550    }
551
552    /// The switch finds a block's end by counting bracket depth and honours no
553    /// escape while doing so, so a value closing a bracket it never opened ends
554    /// the block early and the rest becomes dial-string text.
555    #[test]
556    fn an_unbalanced_bracket_is_refused_while_a_balanced_one_is_not() {
557        assert!("{k=oops}extra}"
558            .parse::<Variables>()
559            .is_err());
560
561        let balanced: Variables = "{k=${some_var}}"
562            .parse()
563            .expect("a balanced ${...} is ordinary and must parse");
564        assert_eq!(balanced.get("k"), Some("${some_var}"));
565    }
566
567    /// A chosen separator is what carries a comma through a value that was
568    /// expanded before the block was parsed, so the comma must survive as
569    /// ordinary text rather than picking up an escape.
570    #[test]
571    fn chosen_separator_leaves_commas_alone() {
572        let mut vars = Variables::new(VariablesType::Default);
573        vars.insert("codecs", "PCMA,PCMU,G729");
574        let vars = vars
575            .with_separator(':')
576            .unwrap();
577
578        assert_eq!(vars.to_string(), "{^^:codecs=PCMA,PCMU,G729}");
579        assert_eq!(vars.separator(), Some(':'));
580    }
581
582    #[test]
583    fn separator_that_cannot_delimit_the_block_is_refused() {
584        let vars = Variables::new(VariablesType::Channel);
585        for sep in ['[', ']', '=', '^'] {
586            assert!(
587                vars.clone()
588                    .with_separator(sep)
589                    .is_err(),
590                "accepted {sep:?}"
591            );
592        }
593    }
594
595    /// The parser has to refuse what the builder refuses, `^` included:
596    /// accepting a block no render of this crate can reproduce hands the caller
597    /// a value that changes when it is written back out.
598    #[test]
599    fn the_parser_refuses_every_separator_the_builder_does() {
600        for sep in ['[', ']', '=', '^'] {
601            assert!(
602                format!("[^^{sep}a=1{sep}b=2]")
603                    .parse::<Variables>()
604                    .is_err(),
605                "parser accepted {sep:?}"
606            );
607        }
608    }
609
610    /// Refusing here is the whole point: a value carrying the separator would
611    /// split into a pair that was never written, and the switch reports nothing.
612    #[test]
613    fn separator_already_present_in_a_value_is_refused() {
614        let mut vars = Variables::new(VariablesType::Default);
615        vars.insert("uri", "sip:bob@example.com");
616        let err = vars
617            .with_separator(':')
618            .unwrap_err()
619            .to_string();
620        assert!(
621            err.contains("uri"),
622            "error does not name the variable: {err}"
623        );
624    }
625
626    /// Measured against a live switch, with two awkward values in one block —
627    /// a block carrying only one is more forgiving and hides the failure.
628    /// Sweeping the backslash count, each carrier succeeds at counts the other
629    /// fails, so these forms are the wire contract and not a preference.
630    #[test]
631    fn escaping_pins_the_measured_wire_forms() {
632        let cases = [
633            (DialStringCarrier::Dialplan, "it's", r"it\\'s"),
634            (DialStringCarrier::EslApi, "it's", r"it\\\'s"),
635            (DialStringCarrier::Dialplan, r"a\nb", r"a\\\\\\\\nb"),
636            (DialStringCarrier::EslApi, r"a\nb", r"a\\\\\\\\nb"),
637            (DialStringCarrier::Dialplan, "a,b", r"a\,b"),
638            (DialStringCarrier::EslApi, "a,b", r"a\,b"),
639        ];
640        for (carrier, value, want) in cases {
641            assert_eq!(
642                escape_value(value, carrier, true),
643                want,
644                "{value:?} for {carrier:?}"
645            );
646        }
647    }
648
649    /// A `^^` block reaches the switch's tokenizer as often as the comma form,
650    /// so every rule above still holds there; only the comma stops being the
651    /// separator and so stops being escaped.
652    #[test]
653    fn a_chosen_separator_changes_only_the_comma() {
654        let cases = [
655            (DialStringCarrier::Dialplan, "it's", r"it\\'s"),
656            (DialStringCarrier::EslApi, "it's", r"it\\\'s"),
657            (DialStringCarrier::Dialplan, r"a\nb", r"a\\\\\\\\nb"),
658            (DialStringCarrier::EslApi, r"a\nb", r"a\\\\\\\\nb"),
659            (DialStringCarrier::Dialplan, "a,b", "a,b"),
660            (DialStringCarrier::EslApi, "a,b", "a,b"),
661        ];
662        for (carrier, value, want) in cases {
663            assert_eq!(
664                escape_value(value, carrier, false),
665                want,
666                "{value:?} for {carrier:?}"
667            );
668        }
669    }
670
671    /// The crate exists to drive `api originate`, so a block rendered with no
672    /// carrier named is rendered for that one.
673    #[test]
674    fn display_defaults_to_the_api_carrier() {
675        let mut vars = Variables::new(VariablesType::Default);
676        vars.insert("k", "it's");
677        assert_eq!(vars.to_string(), r"{k=it\\\'s}");
678        assert_eq!(
679            vars.display_for(DialStringCarrier::Dialplan)
680                .to_string(),
681            r"{k=it\\'s}"
682        );
683    }
684
685    #[test]
686    fn round_trips_at_either_carrier() {
687        for carrier in [DialStringCarrier::EslApi, DialStringCarrier::Dialplan] {
688            for value in ["it's", r"a\,b", r"C:\path", "a,b", "with space"] {
689                let mut vars = Variables::new(VariablesType::Default);
690                vars.insert("k", value);
691                vars.insert("after", "sentinel");
692                let rendered = vars
693                    .display_for(carrier)
694                    .to_string();
695
696                let back = Variables::parse_for(&rendered, carrier).unwrap_or_else(|e| {
697                    panic!("{value:?} for {carrier:?} rendered {rendered}: {e}")
698                });
699                assert_eq!(back.get("k"), Some(value), "rendered {rendered}");
700                assert_eq!(back.get("after"), Some("sentinel"), "rendered {rendered}");
701            }
702        }
703    }
704
705    /// A chosen separator changes which character needs escaping, not whether
706    /// the block is escape-processed: the switch runs the same tokenizer over
707    /// it, so a value still comes back through the same undoing.
708    #[test]
709    fn separated_block_round_trips_at_either_carrier() {
710        for carrier in [DialStringCarrier::EslApi, DialStringCarrier::Dialplan] {
711            for value in ["it's", "a,b", r"C:\path", r"a\nb", "with space"] {
712                let mut vars = Variables::new(VariablesType::Default);
713                vars.insert("k", value);
714                vars.insert("after", "sentinel");
715                let vars = vars
716                    .with_separator('~')
717                    .expect("'~' appears in none of these values");
718                let rendered = vars
719                    .display_for(carrier)
720                    .to_string();
721
722                let back = Variables::parse_for(&rendered, carrier).unwrap_or_else(|e| {
723                    panic!("{value:?} for {carrier:?} rendered {rendered}: {e}")
724                });
725                assert_eq!(back.get("k"), Some(value), "rendered {rendered}");
726                assert_eq!(back.get("after"), Some("sentinel"), "rendered {rendered}");
727            }
728        }
729    }
730
731    /// `split_unescaped_commas` reads a comma behind an even number of
732    /// backslashes as a real separator, so a value ending in a backslash has to
733    /// be written with its own backslash escaped or the writer contradicts the
734    /// reader and the block no longer parses.
735    #[test]
736    fn value_with_backslash_round_trips() {
737        for value in [r"a\,b", r"C:\path", r"trailing\", r"\\", r"a\nb"] {
738            let mut vars = Variables::new(VariablesType::Default);
739            vars.insert("k", value);
740            vars.insert("after", "sentinel");
741            let rendered = vars.to_string();
742
743            let back: Variables = rendered
744                .parse()
745                .unwrap_or_else(|e| {
746                    panic!("{value:?} rendered {rendered} and failed to parse: {e}")
747                });
748            assert_eq!(back.get("k"), Some(value), "rendered {rendered}");
749            assert_eq!(
750                back.get("after"),
751                Some("sentinel"),
752                "value {value:?} ate the next variable: {rendered}"
753            );
754        }
755    }
756
757    /// A variable block holds dialled numbers and passthrough header values, so a
758    /// malformed part is reported by position.
759    #[test]
760    fn missing_equals_error_omits_the_fragment() {
761        let msg = "{origination_caller_id_number=15551234567,15550009999}"
762            .parse::<Variables>()
763            .unwrap_err()
764            .to_string();
765        assert!(
766            !msg.contains("15550009999"),
767            "error quoted its input: {msg}"
768        );
769        assert!(
770            msg.contains("variable 1"),
771            "error does not name the part: {msg}"
772        );
773    }
774
775    #[test]
776    fn unknown_delimiters_error_omits_the_block() {
777        let msg = "(origination_caller_id_number=15551234567)"
778            .parse::<Variables>()
779            .unwrap_err()
780            .to_string();
781        assert!(
782            !msg.contains("15551234567"),
783            "error quoted its input: {msg}"
784        );
785    }
786
787    #[test]
788    fn variables_standard_chars() {
789        let mut vars = Variables::new(VariablesType::Default);
790        vars.insert("test_key", "this_value");
791        let result = vars.to_string();
792        assert!(result.contains("test_key"));
793        assert!(result.contains("this_value"));
794    }
795
796    #[test]
797    fn variables_comma_escaped() {
798        let mut vars = Variables::new(VariablesType::Default);
799        vars.insert("test_key", "this,is,a,value");
800        let result = vars.to_string();
801        assert!(result.contains("\\,"));
802    }
803
804    #[test]
805    fn variables_spaces_quoted() {
806        let mut vars = Variables::new(VariablesType::Default);
807        vars.insert("test_key", "this is a value");
808        let result = vars.to_string();
809        assert_eq!(
810            result
811                .matches('\'')
812                .count(),
813            2
814        );
815    }
816
817    #[test]
818    fn variables_single_quote_escaped() {
819        let mut vars = Variables::new(VariablesType::Default);
820        vars.insert("test_key", "let's_this_be_a_value");
821        let result = vars.to_string();
822        assert!(result.contains("\\'"));
823    }
824
825    #[test]
826    fn variables_enterprise_delimiters() {
827        let mut vars = Variables::new(VariablesType::Enterprise);
828        vars.insert("k", "v");
829        let result = vars.to_string();
830        assert!(result.starts_with('<'));
831        assert!(result.ends_with('>'));
832    }
833
834    #[test]
835    fn variables_channel_delimiters() {
836        let mut vars = Variables::new(VariablesType::Channel);
837        vars.insert("k", "v");
838        let result = vars.to_string();
839        assert!(result.starts_with('['));
840        assert!(result.ends_with(']'));
841    }
842
843    #[test]
844    fn variables_default_delimiters() {
845        let mut vars = Variables::new(VariablesType::Default);
846        vars.insert("k", "v");
847        let result = vars.to_string();
848        assert!(result.starts_with('{'));
849        assert!(result.ends_with('}'));
850    }
851
852    #[test]
853    fn variables_parse_round_trip() {
854        let mut vars = Variables::new(VariablesType::Default);
855        vars.insert("origination_caller_id_number", "9005551212");
856        vars.insert("sip_h_Call-Info", "<url>;meta=123,<uri>");
857        let s = vars.to_string();
858        let parsed: Variables = s
859            .parse()
860            .unwrap();
861        assert_eq!(
862            parsed.get("origination_caller_id_number"),
863            Some("9005551212")
864        );
865        assert_eq!(parsed.get("sip_h_Call-Info"), Some("<url>;meta=123,<uri>"));
866    }
867
868    #[test]
869    fn split_unescaped_basic() {
870        assert_eq!(split_unescaped("a,b,c", ','), vec!["a", "b", "c"]);
871        assert_eq!(split_unescaped("a~b~c", '~'), vec!["a", "b", "c"]);
872    }
873
874    #[test]
875    fn split_unescaped_escaped() {
876        assert_eq!(split_unescaped(r"a\,b,c", ','), vec![r"a\,b", "c"]);
877    }
878
879    #[test]
880    fn split_unescaped_double_backslash() {
881        // \\, = escaped backslash + comma delimiter
882        assert_eq!(split_unescaped(r"a\\,b", ','), vec![r"a\\", "b"]);
883    }
884
885    #[test]
886    fn split_unescaped_triple_backslash() {
887        // \\\, = escaped backslash + escaped comma (no split)
888        assert_eq!(split_unescaped(r"a\\\,b", ','), vec![r"a\\\,b"]);
889    }
890
891    #[test]
892    fn variables_caret_caret_separator() {
893        let vars: Variables =
894            "[^^:sip_invite_domain=pbx.example.com:presence_id=1211@pbx.example.com]"
895                .parse()
896                .unwrap();
897        assert_eq!(vars.scope(), VariablesType::Channel);
898        assert_eq!(vars.get("sip_invite_domain"), Some("pbx.example.com"));
899        assert_eq!(vars.get("presence_id"), Some("1211@pbx.example.com"));
900    }
901
902    #[test]
903    fn variables_caret_caret_display_uses_canonical_comma() {
904        let vars: Variables = "[^^:a=1:b=2]"
905            .parse()
906            .unwrap();
907        assert_eq!(vars.to_string(), "[a=1,b=2]");
908    }
909
910    #[test]
911    fn variables_caret_caret_default_scope() {
912        let vars: Variables = "{^^|x=1|y=2}"
913            .parse()
914            .unwrap();
915        assert_eq!(vars.scope(), VariablesType::Default);
916        assert_eq!(vars.get("x"), Some("1"));
917        assert_eq!(vars.get("y"), Some("2"));
918    }
919
920    #[test]
921    fn variables_caret_caret_enterprise_scope() {
922        let vars: Variables = "<^^;a=1;b=2>"
923            .parse()
924            .unwrap();
925        assert_eq!(vars.scope(), VariablesType::Enterprise);
926        assert_eq!(vars.get("a"), Some("1"));
927    }
928
929    /// The switch consumes a backslash only before a quote, another backslash,
930    /// the separator in force, or a character it has an escape for -- so in a
931    /// block separated on something else, `\,` is two literal characters.
932    #[test]
933    fn variables_caret_caret_keeps_an_escaped_comma_literal() {
934        let vars: Variables = r"[^^:key=val\,ue:other=x]"
935            .parse()
936            .unwrap();
937        assert_eq!(vars.get("key"), Some(r"val\,ue"));
938    }
939
940    #[test]
941    fn variables_caret_caret_values_with_commas() {
942        let vars: Variables = "[^^|sip_h_X-Call-Info=<urn:foo>;purpose=bar,<urn:baz>|other=val]"
943            .parse()
944            .unwrap();
945        assert_eq!(
946            vars.get("sip_h_X-Call-Info"),
947            Some("<urn:foo>;purpose=bar,<urn:baz>")
948        );
949        assert_eq!(vars.get("other"), Some("val"));
950    }
951
952    #[test]
953    fn variables_caret_caret_empty_vars() {
954        let vars: Variables = "[^^:]"
955            .parse()
956            .unwrap();
957        assert!(vars.is_empty());
958        assert_eq!(vars.scope(), VariablesType::Channel);
959    }
960
961    #[test]
962    fn variables_caret_caret_missing_separator() {
963        assert!("[^^]"
964            .parse::<Variables>()
965            .is_err());
966    }
967
968    #[test]
969    fn variables_caret_caret_closing_bracket_as_sep() {
970        assert!("[^^]]"
971            .parse::<Variables>()
972            .is_err());
973    }
974
975    #[test]
976    fn variables_caret_caret_equals_as_sep() {
977        assert!("[^^=a=1]"
978            .parse::<Variables>()
979            .is_err());
980    }
981
982    #[test]
983    fn variables_from_str_empty_block() {
984        let result = "{}".parse::<Variables>();
985        assert!(
986            result.is_ok(),
987            "empty variable block should parse successfully"
988        );
989        let vars = result.unwrap();
990        assert!(
991            vars.is_empty(),
992            "parsed empty block should have no variables"
993        );
994    }
995
996    #[test]
997    fn variables_from_str_empty_channel_block() {
998        let result = "[]".parse::<Variables>();
999        assert!(result.is_ok());
1000        let vars = result.unwrap();
1001        assert!(vars.is_empty());
1002        assert_eq!(vars.scope(), VariablesType::Channel);
1003    }
1004
1005    #[test]
1006    fn variables_from_str_empty_enterprise_block() {
1007        let result = "<>".parse::<Variables>();
1008        assert!(result.is_ok());
1009        let vars = result.unwrap();
1010        assert!(vars.is_empty());
1011        assert_eq!(vars.scope(), VariablesType::Enterprise);
1012    }
1013
1014    #[test]
1015    fn serde_variables_type() {
1016        let json = serde_json::to_string(&VariablesType::Enterprise).unwrap();
1017        assert_eq!(json, "\"enterprise\"");
1018        let parsed: VariablesType = serde_json::from_str(&json).unwrap();
1019        assert_eq!(parsed, VariablesType::Enterprise);
1020    }
1021
1022    #[test]
1023    fn serde_variables_flat_default() {
1024        let mut vars = Variables::new(VariablesType::Default);
1025        vars.insert("key1", "val1");
1026        vars.insert("key2", "val2");
1027        let json = serde_json::to_string(&vars).unwrap();
1028        let parsed: Variables = serde_json::from_str(&json).unwrap();
1029        assert_eq!(parsed.scope(), VariablesType::Default);
1030        assert_eq!(parsed.get("key1"), Some("val1"));
1031        assert_eq!(parsed.get("key2"), Some("val2"));
1032    }
1033
1034    #[test]
1035    fn serde_variables_scoped_enterprise() {
1036        let mut vars = Variables::new(VariablesType::Enterprise);
1037        vars.insert("key1", "val1");
1038        let json = serde_json::to_string(&vars).unwrap();
1039        assert!(json.contains("\"enterprise\""));
1040        let parsed: Variables = serde_json::from_str(&json).unwrap();
1041        assert_eq!(parsed.scope(), VariablesType::Enterprise);
1042        assert_eq!(parsed.get("key1"), Some("val1"));
1043    }
1044
1045    #[test]
1046    fn serde_variables_flat_map_deserializes_as_default() {
1047        let json = r#"{"key1":"val1","key2":"val2"}"#;
1048        let vars: Variables = serde_json::from_str(json).unwrap();
1049        assert_eq!(vars.scope(), VariablesType::Default);
1050        assert_eq!(vars.get("key1"), Some("val1"));
1051        assert_eq!(vars.get("key2"), Some("val2"));
1052    }
1053
1054    #[test]
1055    fn serde_variables_scoped_deserializes() {
1056        let json = r#"{"scope":"channel","vars":{"k":"v"}}"#;
1057        let vars: Variables = serde_json::from_str(json).unwrap();
1058        assert_eq!(vars.scope(), VariablesType::Channel);
1059        assert_eq!(vars.get("k"), Some("v"));
1060    }
1061}