Skip to main content

freeswitch_types/variables/
esl_array.rs

1use std::fmt;
2
3const ARRAY_HEADER: &str = "ARRAY::";
4const ARRAY_SEPARATOR: &str = "|:";
5
6/// Maximum items accepted by [`EslArray::parse`].
7///
8/// Matches the index bound in FreeSWITCH `switch_event.c`
9/// (`switch_event_base_add_header`, `if (index > -1 && index <= 4000)`).
10pub const MAX_ARRAY_ITEMS: usize = 4000;
11
12/// Errors from [`EslArray::parse`].
13#[derive(Debug, Clone, PartialEq, Eq)]
14#[non_exhaustive]
15pub enum EslArrayError {
16    /// Input did not start with the `ARRAY::` prefix.
17    MissingPrefix,
18    /// Item count exceeds [`MAX_ARRAY_ITEMS`].
19    TooManyItems {
20        /// Actual number of items found.
21        count: usize,
22        /// Configured maximum.
23        max: usize,
24    },
25}
26
27impl fmt::Display for EslArrayError {
28    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
29        match self {
30            Self::MissingPrefix => f.write_str("missing ARRAY:: prefix"),
31            Self::TooManyItems { count, max } => {
32                write!(f, "array has {count} items, maximum is {max}")
33            }
34        }
35    }
36}
37
38impl std::error::Error for EslArrayError {}
39
40/// Parses FreeSWITCH `ARRAY::item1|:item2|:item3` format
41#[derive(Debug, Clone, PartialEq, Eq)]
42#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
43pub struct EslArray(Vec<String>);
44
45impl EslArray {
46    /// Parse an `ARRAY::` formatted string.
47    ///
48    /// Returns [`EslArrayError::MissingPrefix`] if the prefix is absent, or
49    /// [`EslArrayError::TooManyItems`] if the item count exceeds
50    /// [`MAX_ARRAY_ITEMS`].
51    pub fn parse(s: &str) -> Result<Self, EslArrayError> {
52        let body = s
53            .strip_prefix(ARRAY_HEADER)
54            .ok_or(EslArrayError::MissingPrefix)?;
55        let items: Vec<String> = body
56            .split(ARRAY_SEPARATOR)
57            .map(String::from)
58            .collect();
59        let count = items.len();
60        if count > MAX_ARRAY_ITEMS {
61            return Err(EslArrayError::TooManyItems {
62                count,
63                max: MAX_ARRAY_ITEMS,
64            });
65        }
66        Ok(Self(items))
67    }
68
69    /// Create a new array from a vec of items.
70    pub fn new(items: Vec<String>) -> Self {
71        Self(items)
72    }
73
74    /// Append an item to the end.
75    pub fn push(&mut self, value: String) {
76        self.0
77            .push(value);
78    }
79
80    /// Prepend an item to the front.
81    pub fn unshift(&mut self, value: String) {
82        self.0
83            .insert(0, value);
84    }
85
86    /// The parsed array items.
87    pub fn items(&self) -> &[String] {
88        &self.0
89    }
90
91    /// Number of items in the array.
92    pub fn len(&self) -> usize {
93        self.0
94            .len()
95    }
96
97    /// Returns `true` if the array has no items.
98    pub fn is_empty(&self) -> bool {
99        self.0
100            .is_empty()
101    }
102}
103
104impl<'a> IntoIterator for &'a EslArray {
105    type Item = &'a String;
106    type IntoIter = std::slice::Iter<'a, String>;
107
108    fn into_iter(self) -> Self::IntoIter {
109        self.0
110            .iter()
111    }
112}
113
114impl IntoIterator for EslArray {
115    type Item = String;
116    type IntoIter = std::vec::IntoIter<String>;
117
118    fn into_iter(self) -> Self::IntoIter {
119        self.0
120            .into_iter()
121    }
122}
123
124impl fmt::Display for EslArray {
125    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
126        f.write_str(ARRAY_HEADER)?;
127        for (i, item) in self
128            .0
129            .iter()
130            .enumerate()
131        {
132            if i > 0 {
133                f.write_str(ARRAY_SEPARATOR)?;
134            }
135            f.write_str(item)?;
136        }
137        Ok(())
138    }
139}
140
141#[cfg(test)]
142mod tests {
143    use super::*;
144
145    #[test]
146    fn parse_single_item() {
147        let arr = EslArray::parse("ARRAY::hello").unwrap();
148        assert_eq!(arr.items(), &["hello"]);
149        assert_eq!(arr.len(), 1);
150    }
151
152    #[test]
153    fn parse_multiple_items() {
154        let arr = EslArray::parse("ARRAY::one|:two|:three").unwrap();
155        assert_eq!(arr.items(), &["one", "two", "three"]);
156        assert_eq!(arr.len(), 3);
157    }
158
159    #[test]
160    fn parse_non_array_returns_missing_prefix() {
161        assert!(matches!(
162            EslArray::parse("not an array"),
163            Err(EslArrayError::MissingPrefix)
164        ));
165        assert!(matches!(
166            EslArray::parse(""),
167            Err(EslArrayError::MissingPrefix)
168        ));
169        assert!(matches!(
170            EslArray::parse("ARRAY:"),
171            Err(EslArrayError::MissingPrefix)
172        ));
173    }
174
175    #[test]
176    fn parse_at_max_items_succeeds() {
177        let items: Vec<&str> = (0..MAX_ARRAY_ITEMS)
178            .map(|_| "x")
179            .collect();
180        let input = format!("ARRAY::{}", items.join("|:"));
181        let arr = EslArray::parse(&input).unwrap();
182        assert_eq!(arr.len(), MAX_ARRAY_ITEMS);
183    }
184
185    #[test]
186    fn parse_over_max_items_returns_error() {
187        let items: Vec<&str> = (0..=MAX_ARRAY_ITEMS)
188            .map(|_| "x")
189            .collect();
190        let input = format!("ARRAY::{}", items.join("|:"));
191        assert_eq!(
192            EslArray::parse(&input),
193            Err(EslArrayError::TooManyItems {
194                count: MAX_ARRAY_ITEMS + 1,
195                max: MAX_ARRAY_ITEMS,
196            })
197        );
198    }
199
200    #[test]
201    fn error_display_missing_prefix() {
202        assert_eq!(
203            EslArrayError::MissingPrefix.to_string(),
204            "missing ARRAY:: prefix"
205        );
206    }
207
208    #[test]
209    fn error_display_too_many_items() {
210        let err = EslArrayError::TooManyItems {
211            count: 5000,
212            max: 4000,
213        };
214        assert_eq!(err.to_string(), "array has 5000 items, maximum is 4000");
215    }
216
217    #[test]
218    fn display_round_trip() {
219        let input = "ARRAY::one|:two|:three";
220        let arr = EslArray::parse(input).unwrap();
221        assert_eq!(arr.to_string(), input);
222    }
223
224    #[test]
225    fn display_single_item() {
226        let arr = EslArray::parse("ARRAY::only").unwrap();
227        assert_eq!(arr.to_string(), "ARRAY::only");
228    }
229
230    #[test]
231    fn empty_items_in_array() {
232        let arr = EslArray::parse("ARRAY::|:|:stuff").unwrap();
233        assert_eq!(arr.items(), &["", "", "stuff"]);
234    }
235
236    #[test]
237    fn test_new() {
238        let arr = EslArray::new(vec!["a".into(), "b".into(), "c".into()]);
239        assert_eq!(arr.items(), &["a", "b", "c"]);
240        assert_eq!(arr.len(), 3);
241    }
242
243    #[test]
244    fn test_push() {
245        let mut arr = EslArray::new(vec!["first".into()]);
246        arr.push("second".into());
247        arr.push("third".into());
248        assert_eq!(arr.items(), &["first", "second", "third"]);
249        assert_eq!(arr.to_string(), "ARRAY::first|:second|:third");
250    }
251
252    #[test]
253    fn test_unshift() {
254        let mut arr = EslArray::new(vec!["last".into()]);
255        arr.unshift("middle".into());
256        arr.unshift("first".into());
257        assert_eq!(arr.items(), &["first", "middle", "last"]);
258        assert_eq!(arr.to_string(), "ARRAY::first|:middle|:last");
259    }
260
261    #[test]
262    fn parse_sip_uris_with_colons() {
263        let input = "ARRAY::sip:alice@atlanta.example.com|:sip:bob@biloxi.example.com";
264        let arr = EslArray::parse(input).unwrap();
265        assert_eq!(
266            arr.items(),
267            &[
268                "sip:alice@atlanta.example.com",
269                "sip:bob@biloxi.example.com"
270            ]
271        );
272        assert_eq!(arr.to_string(), input);
273    }
274
275    #[test]
276    fn parse_sip_uris_with_angle_brackets_and_params() {
277        let input = "ARRAY::<sip:+15551234567@gw.example.com;user=phone>|:<tel:+15559876543>";
278        let arr = EslArray::parse(input).unwrap();
279        assert_eq!(arr.len(), 2);
280        assert_eq!(
281            arr.items()[0],
282            "<sip:+15551234567@gw.example.com;user=phone>"
283        );
284        assert_eq!(arr.items()[1], "<tel:+15559876543>");
285        assert_eq!(arr.to_string(), input);
286    }
287}