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/// Values containing commas are escaped with `\,`, single quotes with `\'`,
40/// and values with spaces are wrapped in single quotes.
41///
42/// # Serde format
43///
44/// [`Default`](VariablesType::Default) scope serializes as a flat JSON map:
45/// `{"key": "value", ...}`. Non-default scopes serialize as
46/// `{"scope": "Enterprise", "vars": {"key": "value"}}`.
47/// Deserialization accepts both formats; a flat map implies `Default` scope.
48#[derive(Debug, Clone, PartialEq, Eq)]
49pub struct Variables {
50    vars_type: VariablesType,
51    inner: IndexMap<String, String>,
52}
53
54pub(super) fn escape_value(value: &str) -> String {
55    let escaped = value
56        .replace('\'', "\\'")
57        .replace(',', "\\,");
58    if escaped.contains(' ') {
59        format!("'{}'", escaped)
60    } else {
61        escaped
62    }
63}
64
65fn unescape_value(value: &str) -> String {
66    let s = value
67        .strip_prefix('\'')
68        .and_then(|s| s.strip_suffix('\''))
69        .unwrap_or(value);
70    s.replace("\\,", ",")
71        .replace("\\'", "'")
72}
73
74impl Variables {
75    /// Create an empty variable set with the given scope.
76    pub fn new(vars_type: VariablesType) -> Self {
77        Self {
78            vars_type,
79            inner: IndexMap::new(),
80        }
81    }
82
83    /// Create from an existing set of key-value pairs.
84    pub fn with_vars(
85        vars_type: VariablesType,
86        vars: impl IntoIterator<Item = (impl Into<String>, impl Into<String>)>,
87    ) -> Self {
88        Self {
89            vars_type,
90            inner: vars
91                .into_iter()
92                .map(|(k, v)| (k.into(), v.into()))
93                .collect(),
94        }
95    }
96
97    /// Insert or overwrite a variable.
98    pub fn insert(&mut self, key: impl Into<String>, value: impl Into<String>) {
99        self.inner
100            .insert(key.into(), value.into());
101    }
102
103    /// Remove a variable by name, returning its value if it existed.
104    pub fn remove(&mut self, key: &str) -> Option<String> {
105        self.inner
106            .shift_remove(key)
107    }
108
109    /// Look up a variable by name.
110    pub fn get(&self, key: &str) -> Option<&str> {
111        self.inner
112            .get(key)
113            .map(|s| s.as_str())
114    }
115
116    /// Whether the set contains no variables.
117    pub fn is_empty(&self) -> bool {
118        self.inner
119            .is_empty()
120    }
121
122    /// Number of variables.
123    pub fn len(&self) -> usize {
124        self.inner
125            .len()
126    }
127
128    /// Variable scope (Enterprise, Default, or Channel).
129    pub fn scope(&self) -> VariablesType {
130        self.vars_type
131    }
132
133    /// Change the variable scope.
134    pub fn set_scope(&mut self, scope: VariablesType) {
135        self.vars_type = scope;
136    }
137
138    /// Iterate over key-value pairs in insertion order.
139    pub fn iter(&self) -> impl Iterator<Item = (&str, &str)> {
140        self.inner
141            .iter()
142            .map(|(k, v)| (k.as_str(), v.as_str()))
143    }
144
145    /// Mutable iterator over key-value pairs in insertion order.
146    pub fn iter_mut(&mut self) -> impl Iterator<Item = (&str, &mut String)> {
147        self.inner
148            .iter_mut()
149            .map(|(k, v)| (k.as_str(), v))
150    }
151
152    /// Mutable iterator over values in insertion order.
153    pub fn values_mut(&mut self) -> impl Iterator<Item = &mut String> {
154        self.inner
155            .values_mut()
156    }
157}
158
159#[cfg(feature = "serde")]
160impl serde::Serialize for Variables {
161    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
162        if self.vars_type == VariablesType::Default {
163            self.inner
164                .serialize(serializer)
165        } else {
166            use serde::ser::SerializeStruct;
167            let mut s = serializer.serialize_struct("Variables", 2)?;
168            s.serialize_field("scope", &self.vars_type)?;
169            s.serialize_field("vars", &self.inner)?;
170            s.end()
171        }
172    }
173}
174
175#[cfg(feature = "serde")]
176impl<'de> serde::Deserialize<'de> for Variables {
177    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
178        #[derive(serde::Deserialize)]
179        #[serde(untagged)]
180        enum VariablesRepr {
181            Scoped {
182                scope: VariablesType,
183                vars: IndexMap<String, String>,
184            },
185            Flat(IndexMap<String, String>),
186        }
187
188        match VariablesRepr::deserialize(deserializer)? {
189            VariablesRepr::Scoped { scope, vars } => Ok(Self {
190                vars_type: scope,
191                inner: vars,
192            }),
193            VariablesRepr::Flat(map) => Ok(Self {
194                vars_type: VariablesType::Default,
195                inner: map,
196            }),
197        }
198    }
199}
200
201impl fmt::Display for Variables {
202    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
203        let (open, close) = self
204            .vars_type
205            .delimiters();
206        f.write_fmt(format_args!("{}", open))?;
207        for (i, (key, value)) in self
208            .inner
209            .iter()
210            .enumerate()
211        {
212            if i > 0 {
213                f.write_str(",")?;
214            }
215            write!(f, "{}={}", key, escape_value(value))?;
216        }
217        f.write_fmt(format_args!("{}", close))
218    }
219}
220
221impl FromStr for Variables {
222    type Err = OriginateError;
223
224    fn from_str(s: &str) -> Result<Self, Self::Err> {
225        let s = s.trim();
226        if s.len() < 2 {
227            return Err(OriginateError::ParseError(
228                "variable block too short".into(),
229            ));
230        }
231
232        let (vars_type, inner_str) = match (s.as_bytes()[0], s.as_bytes()[s.len() - 1]) {
233            (b'{', b'}') => (VariablesType::Default, &s[1..s.len() - 1]),
234            (b'<', b'>') => (VariablesType::Enterprise, &s[1..s.len() - 1]),
235            (b'[', b']') => (VariablesType::Channel, &s[1..s.len() - 1]),
236            (open, close) => {
237                return Err(OriginateError::ParseError(format!(
238                    "unknown variable delimiters: {:?}..{:?}",
239                    open as char, close as char
240                )));
241            }
242        };
243
244        let mut inner = IndexMap::new();
245        if !inner_str.is_empty() {
246            if let Some(rest) = inner_str.strip_prefix("^^") {
247                let sep = rest
248                    .chars()
249                    .next()
250                    .ok_or_else(|| {
251                        OriginateError::ParseError("^^ without separator character".into())
252                    })?;
253                let (_, close) = vars_type.delimiters();
254                if sep == close || sep == '=' {
255                    return Err(OriginateError::ParseError(format!(
256                        "invalid ^^ separator: '{sep}'"
257                    )));
258                }
259                let var_str = &rest[sep.len_utf8()..];
260                if !var_str.is_empty() {
261                    for (i, part) in var_str
262                        .split(sep)
263                        .enumerate()
264                    {
265                        let (key, value) = part
266                            .split_once('=')
267                            .ok_or_else(|| {
268                                OriginateError::ParseError(format!("missing = in variable {i}"))
269                            })?;
270                        inner.insert(key.to_string(), value.to_string());
271                    }
272                }
273            } else {
274                for (i, part) in split_unescaped_commas(inner_str)
275                    .into_iter()
276                    .enumerate()
277                {
278                    let (key, value) = part
279                        .split_once('=')
280                        .ok_or_else(|| {
281                            OriginateError::ParseError(format!("missing = in variable {i}"))
282                        })?;
283                    inner.insert(key.to_string(), unescape_value(value));
284                }
285            }
286        }
287
288        Ok(Self { vars_type, inner })
289    }
290}
291
292/// Split on commas that are not escaped by a backslash.
293///
294/// A comma preceded by an odd number of backslashes is escaped (e.g. `\,`).
295/// A comma preceded by an even number of backslashes is a real split point
296/// (e.g. `\\,` means escaped backslash followed by comma delimiter).
297pub(super) fn split_unescaped_commas(s: &str) -> Vec<&str> {
298    let mut parts = Vec::new();
299    let mut start = 0;
300    let bytes = s.as_bytes();
301
302    for i in 0..bytes.len() {
303        if bytes[i] == b',' {
304            let mut backslashes = 0;
305            let mut j = i;
306            while j > 0 && bytes[j - 1] == b'\\' {
307                backslashes += 1;
308                j -= 1;
309            }
310            if backslashes % 2 == 0 {
311                parts.push(&s[start..i]);
312                start = i + 1;
313            }
314        }
315    }
316    parts.push(&s[start..]);
317    parts
318}
319
320#[cfg(test)]
321mod tests {
322    use super::*;
323
324    /// A variable block holds dialled numbers and passthrough header values, so a
325    /// malformed part is reported by position.
326    #[test]
327    fn missing_equals_error_omits_the_fragment() {
328        let msg = "{origination_caller_id_number=15551234567,15550009999}"
329            .parse::<Variables>()
330            .unwrap_err()
331            .to_string();
332        assert!(
333            !msg.contains("15550009999"),
334            "error quoted its input: {msg}"
335        );
336        assert!(
337            msg.contains("variable 1"),
338            "error does not name the part: {msg}"
339        );
340    }
341
342    #[test]
343    fn unknown_delimiters_error_omits_the_block() {
344        let msg = "(origination_caller_id_number=15551234567)"
345            .parse::<Variables>()
346            .unwrap_err()
347            .to_string();
348        assert!(
349            !msg.contains("15551234567"),
350            "error quoted its input: {msg}"
351        );
352    }
353
354    #[test]
355    fn variables_standard_chars() {
356        let mut vars = Variables::new(VariablesType::Default);
357        vars.insert("test_key", "this_value");
358        let result = vars.to_string();
359        assert!(result.contains("test_key"));
360        assert!(result.contains("this_value"));
361    }
362
363    #[test]
364    fn variables_comma_escaped() {
365        let mut vars = Variables::new(VariablesType::Default);
366        vars.insert("test_key", "this,is,a,value");
367        let result = vars.to_string();
368        assert!(result.contains("\\,"));
369    }
370
371    #[test]
372    fn variables_spaces_quoted() {
373        let mut vars = Variables::new(VariablesType::Default);
374        vars.insert("test_key", "this is a value");
375        let result = vars.to_string();
376        assert_eq!(
377            result
378                .matches('\'')
379                .count(),
380            2
381        );
382    }
383
384    #[test]
385    fn variables_single_quote_escaped() {
386        let mut vars = Variables::new(VariablesType::Default);
387        vars.insert("test_key", "let's_this_be_a_value");
388        let result = vars.to_string();
389        assert!(result.contains("\\'"));
390    }
391
392    #[test]
393    fn variables_enterprise_delimiters() {
394        let mut vars = Variables::new(VariablesType::Enterprise);
395        vars.insert("k", "v");
396        let result = vars.to_string();
397        assert!(result.starts_with('<'));
398        assert!(result.ends_with('>'));
399    }
400
401    #[test]
402    fn variables_channel_delimiters() {
403        let mut vars = Variables::new(VariablesType::Channel);
404        vars.insert("k", "v");
405        let result = vars.to_string();
406        assert!(result.starts_with('['));
407        assert!(result.ends_with(']'));
408    }
409
410    #[test]
411    fn variables_default_delimiters() {
412        let mut vars = Variables::new(VariablesType::Default);
413        vars.insert("k", "v");
414        let result = vars.to_string();
415        assert!(result.starts_with('{'));
416        assert!(result.ends_with('}'));
417    }
418
419    #[test]
420    fn variables_parse_round_trip() {
421        let mut vars = Variables::new(VariablesType::Default);
422        vars.insert("origination_caller_id_number", "9005551212");
423        vars.insert("sip_h_Call-Info", "<url>;meta=123,<uri>");
424        let s = vars.to_string();
425        let parsed: Variables = s
426            .parse()
427            .unwrap();
428        assert_eq!(
429            parsed.get("origination_caller_id_number"),
430            Some("9005551212")
431        );
432        assert_eq!(parsed.get("sip_h_Call-Info"), Some("<url>;meta=123,<uri>"));
433    }
434
435    #[test]
436    fn split_unescaped_commas_basic() {
437        assert_eq!(split_unescaped_commas("a,b,c"), vec!["a", "b", "c"]);
438    }
439
440    #[test]
441    fn split_unescaped_commas_escaped() {
442        assert_eq!(split_unescaped_commas(r"a\,b,c"), vec![r"a\,b", "c"]);
443    }
444
445    #[test]
446    fn split_unescaped_commas_double_backslash() {
447        // \\, = escaped backslash + comma delimiter
448        assert_eq!(split_unescaped_commas(r"a\\,b"), vec![r"a\\", "b"]);
449    }
450
451    #[test]
452    fn split_unescaped_commas_triple_backslash() {
453        // \\\, = escaped backslash + escaped comma (no split)
454        assert_eq!(split_unescaped_commas(r"a\\\,b"), vec![r"a\\\,b"]);
455    }
456
457    #[test]
458    fn variables_caret_caret_separator() {
459        let vars: Variables =
460            "[^^:sip_invite_domain=pbx.example.com:presence_id=1211@pbx.example.com]"
461                .parse()
462                .unwrap();
463        assert_eq!(vars.scope(), VariablesType::Channel);
464        assert_eq!(vars.get("sip_invite_domain"), Some("pbx.example.com"));
465        assert_eq!(vars.get("presence_id"), Some("1211@pbx.example.com"));
466    }
467
468    #[test]
469    fn variables_caret_caret_display_uses_canonical_comma() {
470        let vars: Variables = "[^^:a=1:b=2]"
471            .parse()
472            .unwrap();
473        assert_eq!(vars.to_string(), "[a=1,b=2]");
474    }
475
476    #[test]
477    fn variables_caret_caret_default_scope() {
478        let vars: Variables = "{^^|x=1|y=2}"
479            .parse()
480            .unwrap();
481        assert_eq!(vars.scope(), VariablesType::Default);
482        assert_eq!(vars.get("x"), Some("1"));
483        assert_eq!(vars.get("y"), Some("2"));
484    }
485
486    #[test]
487    fn variables_caret_caret_enterprise_scope() {
488        let vars: Variables = "<^^;a=1;b=2>"
489            .parse()
490            .unwrap();
491        assert_eq!(vars.scope(), VariablesType::Enterprise);
492        assert_eq!(vars.get("a"), Some("1"));
493    }
494
495    #[test]
496    fn variables_caret_caret_no_unescape() {
497        let vars: Variables = r"[^^:key=val\,ue:other=x]"
498            .parse()
499            .unwrap();
500        assert_eq!(vars.get("key"), Some(r"val\,ue"));
501    }
502
503    #[test]
504    fn variables_caret_caret_values_with_commas() {
505        let vars: Variables = "[^^|sip_h_X-Call-Info=<urn:foo>;purpose=bar,<urn:baz>|other=val]"
506            .parse()
507            .unwrap();
508        assert_eq!(
509            vars.get("sip_h_X-Call-Info"),
510            Some("<urn:foo>;purpose=bar,<urn:baz>")
511        );
512        assert_eq!(vars.get("other"), Some("val"));
513    }
514
515    #[test]
516    fn variables_caret_caret_empty_vars() {
517        let vars: Variables = "[^^:]"
518            .parse()
519            .unwrap();
520        assert!(vars.is_empty());
521        assert_eq!(vars.scope(), VariablesType::Channel);
522    }
523
524    #[test]
525    fn variables_caret_caret_missing_separator() {
526        assert!("[^^]"
527            .parse::<Variables>()
528            .is_err());
529    }
530
531    #[test]
532    fn variables_caret_caret_closing_bracket_as_sep() {
533        assert!("[^^]]"
534            .parse::<Variables>()
535            .is_err());
536    }
537
538    #[test]
539    fn variables_caret_caret_equals_as_sep() {
540        assert!("[^^=a=1]"
541            .parse::<Variables>()
542            .is_err());
543    }
544
545    #[test]
546    fn variables_from_str_empty_block() {
547        let result = "{}".parse::<Variables>();
548        assert!(
549            result.is_ok(),
550            "empty variable block should parse successfully"
551        );
552        let vars = result.unwrap();
553        assert!(
554            vars.is_empty(),
555            "parsed empty block should have no variables"
556        );
557    }
558
559    #[test]
560    fn variables_from_str_empty_channel_block() {
561        let result = "[]".parse::<Variables>();
562        assert!(result.is_ok());
563        let vars = result.unwrap();
564        assert!(vars.is_empty());
565        assert_eq!(vars.scope(), VariablesType::Channel);
566    }
567
568    #[test]
569    fn variables_from_str_empty_enterprise_block() {
570        let result = "<>".parse::<Variables>();
571        assert!(result.is_ok());
572        let vars = result.unwrap();
573        assert!(vars.is_empty());
574        assert_eq!(vars.scope(), VariablesType::Enterprise);
575    }
576
577    #[test]
578    fn serde_variables_type() {
579        let json = serde_json::to_string(&VariablesType::Enterprise).unwrap();
580        assert_eq!(json, "\"enterprise\"");
581        let parsed: VariablesType = serde_json::from_str(&json).unwrap();
582        assert_eq!(parsed, VariablesType::Enterprise);
583    }
584
585    #[test]
586    fn serde_variables_flat_default() {
587        let mut vars = Variables::new(VariablesType::Default);
588        vars.insert("key1", "val1");
589        vars.insert("key2", "val2");
590        let json = serde_json::to_string(&vars).unwrap();
591        let parsed: Variables = serde_json::from_str(&json).unwrap();
592        assert_eq!(parsed.scope(), VariablesType::Default);
593        assert_eq!(parsed.get("key1"), Some("val1"));
594        assert_eq!(parsed.get("key2"), Some("val2"));
595    }
596
597    #[test]
598    fn serde_variables_scoped_enterprise() {
599        let mut vars = Variables::new(VariablesType::Enterprise);
600        vars.insert("key1", "val1");
601        let json = serde_json::to_string(&vars).unwrap();
602        assert!(json.contains("\"enterprise\""));
603        let parsed: Variables = serde_json::from_str(&json).unwrap();
604        assert_eq!(parsed.scope(), VariablesType::Enterprise);
605        assert_eq!(parsed.get("key1"), Some("val1"));
606    }
607
608    #[test]
609    fn serde_variables_flat_map_deserializes_as_default() {
610        let json = r#"{"key1":"val1","key2":"val2"}"#;
611        let vars: Variables = serde_json::from_str(json).unwrap();
612        assert_eq!(vars.scope(), VariablesType::Default);
613        assert_eq!(vars.get("key1"), Some("val1"));
614        assert_eq!(vars.get("key2"), Some("val2"));
615    }
616
617    #[test]
618    fn serde_variables_scoped_deserializes() {
619        let json = r#"{"scope":"channel","vars":{"k":"v"}}"#;
620        let vars: Variables = serde_json::from_str(json).unwrap();
621        assert_eq!(vars.scope(), VariablesType::Channel);
622        assert_eq!(vars.get("k"), Some("v"));
623    }
624}