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