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