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    /// Wire marker every `ARRAY::` value starts with.
47    pub const PREFIX: &'static str = ARRAY_HEADER;
48
49    /// Separator between items after the prefix.
50    pub const SEPARATOR: &'static str = ARRAY_SEPARATOR;
51
52    /// Parse an `ARRAY::` formatted string.
53    ///
54    /// Returns [`EslArrayError::MissingPrefix`] if the prefix is absent, or
55    /// [`EslArrayError::TooManyItems`] if the item count exceeds
56    /// [`MAX_ARRAY_ITEMS`].
57    pub fn parse(s: &str) -> Result<Self, EslArrayError> {
58        let body = s
59            .strip_prefix(ARRAY_HEADER)
60            .ok_or(EslArrayError::MissingPrefix)?;
61        let items: Vec<String> = body
62            .split(ARRAY_SEPARATOR)
63            .map(String::from)
64            .collect();
65        let count = items.len();
66        if count > MAX_ARRAY_ITEMS {
67            return Err(EslArrayError::TooManyItems {
68                count,
69                max: MAX_ARRAY_ITEMS,
70            });
71        }
72        Ok(Self(items))
73    }
74
75    /// Create a new array from a vec of items.
76    pub fn new(items: Vec<String>) -> Self {
77        Self(items)
78    }
79
80    /// Append an item to the end.
81    pub fn push(&mut self, value: String) {
82        self.0
83            .push(value);
84    }
85
86    /// Prepend an item to the front.
87    pub fn unshift(&mut self, value: String) {
88        self.0
89            .insert(0, value);
90    }
91
92    /// The parsed array items.
93    pub fn items(&self) -> &[String] {
94        &self.0
95    }
96
97    /// Number of items in the array.
98    pub fn len(&self) -> usize {
99        self.0
100            .len()
101    }
102
103    /// Returns `true` if the array has no items.
104    pub fn is_empty(&self) -> bool {
105        self.0
106            .is_empty()
107    }
108}
109
110impl<'a> IntoIterator for &'a EslArray {
111    type Item = &'a String;
112    type IntoIter = std::slice::Iter<'a, String>;
113
114    fn into_iter(self) -> Self::IntoIter {
115        self.0
116            .iter()
117    }
118}
119
120impl IntoIterator for EslArray {
121    type Item = String;
122    type IntoIter = std::vec::IntoIter<String>;
123
124    fn into_iter(self) -> Self::IntoIter {
125        self.0
126            .into_iter()
127    }
128}
129
130impl fmt::Display for EslArray {
131    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
132        f.write_str(ARRAY_HEADER)?;
133        for (i, item) in self
134            .0
135            .iter()
136            .enumerate()
137        {
138            if i > 0 {
139                f.write_str(ARRAY_SEPARATOR)?;
140            }
141            f.write_str(item)?;
142        }
143        Ok(())
144    }
145}
146
147#[cfg(test)]
148mod tests {
149    use super::*;
150
151    /// Callers scanning prose for an embedded array need the marker without
152    /// hardcoding it.
153    #[test]
154    fn prefix_is_the_wire_marker() {
155        let input = format!("{}one|:two", EslArray::PREFIX);
156        assert_eq!(
157            EslArray::parse(&input)
158                .unwrap()
159                .items(),
160            &["one", "two"]
161        );
162    }
163
164    #[test]
165    fn parse_single_item() {
166        let arr = EslArray::parse("ARRAY::hello").unwrap();
167        assert_eq!(arr.items(), &["hello"]);
168        assert_eq!(arr.len(), 1);
169    }
170
171    #[test]
172    fn parse_multiple_items() {
173        let arr = EslArray::parse("ARRAY::one|:two|:three").unwrap();
174        assert_eq!(arr.items(), &["one", "two", "three"]);
175        assert_eq!(arr.len(), 3);
176    }
177
178    #[test]
179    fn parse_non_array_returns_missing_prefix() {
180        assert!(matches!(
181            EslArray::parse("not an array"),
182            Err(EslArrayError::MissingPrefix)
183        ));
184        assert!(matches!(
185            EslArray::parse(""),
186            Err(EslArrayError::MissingPrefix)
187        ));
188        assert!(matches!(
189            EslArray::parse("ARRAY:"),
190            Err(EslArrayError::MissingPrefix)
191        ));
192    }
193
194    #[test]
195    fn parse_at_max_items_succeeds() {
196        let items: Vec<&str> = (0..MAX_ARRAY_ITEMS)
197            .map(|_| "x")
198            .collect();
199        let input = format!("ARRAY::{}", items.join("|:"));
200        let arr = EslArray::parse(&input).unwrap();
201        assert_eq!(arr.len(), MAX_ARRAY_ITEMS);
202    }
203
204    #[test]
205    fn parse_over_max_items_returns_error() {
206        let items: Vec<&str> = (0..=MAX_ARRAY_ITEMS)
207            .map(|_| "x")
208            .collect();
209        let input = format!("ARRAY::{}", items.join("|:"));
210        assert_eq!(
211            EslArray::parse(&input),
212            Err(EslArrayError::TooManyItems {
213                count: MAX_ARRAY_ITEMS + 1,
214                max: MAX_ARRAY_ITEMS,
215            })
216        );
217    }
218
219    #[test]
220    fn error_display_missing_prefix() {
221        assert_eq!(
222            EslArrayError::MissingPrefix.to_string(),
223            "missing ARRAY:: prefix"
224        );
225    }
226
227    #[test]
228    fn error_display_too_many_items() {
229        let err = EslArrayError::TooManyItems {
230            count: 5000,
231            max: 4000,
232        };
233        assert_eq!(err.to_string(), "array has 5000 items, maximum is 4000");
234    }
235
236    #[test]
237    fn display_round_trip() {
238        let input = "ARRAY::one|:two|:three";
239        let arr = EslArray::parse(input).unwrap();
240        assert_eq!(arr.to_string(), input);
241    }
242
243    #[test]
244    fn display_single_item() {
245        let arr = EslArray::parse("ARRAY::only").unwrap();
246        assert_eq!(arr.to_string(), "ARRAY::only");
247    }
248
249    #[test]
250    fn empty_items_in_array() {
251        let arr = EslArray::parse("ARRAY::|:|:stuff").unwrap();
252        assert_eq!(arr.items(), &["", "", "stuff"]);
253    }
254
255    #[test]
256    fn new_holds_the_items_given() {
257        let arr = EslArray::new(vec!["a".into(), "b".into(), "c".into()]);
258        assert_eq!(arr.items(), &["a", "b", "c"]);
259        assert_eq!(arr.len(), 3);
260    }
261
262    #[test]
263    fn push_appends() {
264        let mut arr = EslArray::new(vec!["first".into()]);
265        arr.push("second".into());
266        arr.push("third".into());
267        assert_eq!(arr.items(), &["first", "second", "third"]);
268        assert_eq!(arr.to_string(), "ARRAY::first|:second|:third");
269    }
270
271    #[test]
272    fn unshift_prepends() {
273        let mut arr = EslArray::new(vec!["last".into()]);
274        arr.unshift("middle".into());
275        arr.unshift("first".into());
276        assert_eq!(arr.items(), &["first", "middle", "last"]);
277        assert_eq!(arr.to_string(), "ARRAY::first|:middle|:last");
278    }
279
280    #[test]
281    fn parse_sip_uris_with_colons() {
282        let input = "ARRAY::sip:alice@atlanta.example.com|:sip:bob@biloxi.example.com";
283        let arr = EslArray::parse(input).unwrap();
284        assert_eq!(
285            arr.items(),
286            &[
287                "sip:alice@atlanta.example.com",
288                "sip:bob@biloxi.example.com"
289            ]
290        );
291        assert_eq!(arr.to_string(), input);
292    }
293
294    #[test]
295    fn parse_sip_uris_with_angle_brackets_and_params() {
296        let input = "ARRAY::<sip:+15551234567@gw.example.com;user=phone>|:<tel:+15559876543>";
297        let arr = EslArray::parse(input).unwrap();
298        assert_eq!(arr.len(), 2);
299        assert_eq!(
300            arr.items()[0],
301            "<sip:+15551234567@gw.example.com;user=phone>"
302        );
303        assert_eq!(arr.items()[1], "<tel:+15559876543>");
304        assert_eq!(arr.to_string(), input);
305    }
306}