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