use std::fmt;
const ARRAY_HEADER: &str = "ARRAY::";
const ARRAY_SEPARATOR: &str = "|:";
pub const MAX_ARRAY_ITEMS: usize = 4000;
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum EslArrayError {
MissingPrefix,
TooManyItems {
count: usize,
max: usize,
},
}
impl fmt::Display for EslArrayError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::MissingPrefix => f.write_str("missing ARRAY:: prefix"),
Self::TooManyItems { count, max } => {
write!(f, "array has {count} items, maximum is {max}")
}
}
}
}
impl std::error::Error for EslArrayError {}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct EslArray(Vec<String>);
impl EslArray {
pub fn parse(s: &str) -> Result<Self, EslArrayError> {
let body = s
.strip_prefix(ARRAY_HEADER)
.ok_or(EslArrayError::MissingPrefix)?;
let items: Vec<String> = body
.split(ARRAY_SEPARATOR)
.map(String::from)
.collect();
let count = items.len();
if count > MAX_ARRAY_ITEMS {
return Err(EslArrayError::TooManyItems {
count,
max: MAX_ARRAY_ITEMS,
});
}
Ok(Self(items))
}
pub fn new(items: Vec<String>) -> Self {
Self(items)
}
pub fn push(&mut self, value: String) {
self.0
.push(value);
}
pub fn unshift(&mut self, value: String) {
self.0
.insert(0, value);
}
pub fn items(&self) -> &[String] {
&self.0
}
pub fn len(&self) -> usize {
self.0
.len()
}
pub fn is_empty(&self) -> bool {
self.0
.is_empty()
}
}
impl<'a> IntoIterator for &'a EslArray {
type Item = &'a String;
type IntoIter = std::slice::Iter<'a, String>;
fn into_iter(self) -> Self::IntoIter {
self.0
.iter()
}
}
impl IntoIterator for EslArray {
type Item = String;
type IntoIter = std::vec::IntoIter<String>;
fn into_iter(self) -> Self::IntoIter {
self.0
.into_iter()
}
}
impl fmt::Display for EslArray {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(ARRAY_HEADER)?;
for (i, item) in self
.0
.iter()
.enumerate()
{
if i > 0 {
f.write_str(ARRAY_SEPARATOR)?;
}
f.write_str(item)?;
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_single_item() {
let arr = EslArray::parse("ARRAY::hello").unwrap();
assert_eq!(arr.items(), &["hello"]);
assert_eq!(arr.len(), 1);
}
#[test]
fn parse_multiple_items() {
let arr = EslArray::parse("ARRAY::one|:two|:three").unwrap();
assert_eq!(arr.items(), &["one", "two", "three"]);
assert_eq!(arr.len(), 3);
}
#[test]
fn parse_non_array_returns_missing_prefix() {
assert!(matches!(
EslArray::parse("not an array"),
Err(EslArrayError::MissingPrefix)
));
assert!(matches!(
EslArray::parse(""),
Err(EslArrayError::MissingPrefix)
));
assert!(matches!(
EslArray::parse("ARRAY:"),
Err(EslArrayError::MissingPrefix)
));
}
#[test]
fn parse_at_max_items_succeeds() {
let items: Vec<&str> = (0..MAX_ARRAY_ITEMS)
.map(|_| "x")
.collect();
let input = format!("ARRAY::{}", items.join("|:"));
let arr = EslArray::parse(&input).unwrap();
assert_eq!(arr.len(), MAX_ARRAY_ITEMS);
}
#[test]
fn parse_over_max_items_returns_error() {
let items: Vec<&str> = (0..=MAX_ARRAY_ITEMS)
.map(|_| "x")
.collect();
let input = format!("ARRAY::{}", items.join("|:"));
assert_eq!(
EslArray::parse(&input),
Err(EslArrayError::TooManyItems {
count: MAX_ARRAY_ITEMS + 1,
max: MAX_ARRAY_ITEMS,
})
);
}
#[test]
fn error_display_missing_prefix() {
assert_eq!(
EslArrayError::MissingPrefix.to_string(),
"missing ARRAY:: prefix"
);
}
#[test]
fn error_display_too_many_items() {
let err = EslArrayError::TooManyItems {
count: 5000,
max: 4000,
};
assert_eq!(err.to_string(), "array has 5000 items, maximum is 4000");
}
#[test]
fn display_round_trip() {
let input = "ARRAY::one|:two|:three";
let arr = EslArray::parse(input).unwrap();
assert_eq!(arr.to_string(), input);
}
#[test]
fn display_single_item() {
let arr = EslArray::parse("ARRAY::only").unwrap();
assert_eq!(arr.to_string(), "ARRAY::only");
}
#[test]
fn empty_items_in_array() {
let arr = EslArray::parse("ARRAY::|:|:stuff").unwrap();
assert_eq!(arr.items(), &["", "", "stuff"]);
}
#[test]
fn test_new() {
let arr = EslArray::new(vec!["a".into(), "b".into(), "c".into()]);
assert_eq!(arr.items(), &["a", "b", "c"]);
assert_eq!(arr.len(), 3);
}
#[test]
fn test_push() {
let mut arr = EslArray::new(vec!["first".into()]);
arr.push("second".into());
arr.push("third".into());
assert_eq!(arr.items(), &["first", "second", "third"]);
assert_eq!(arr.to_string(), "ARRAY::first|:second|:third");
}
#[test]
fn test_unshift() {
let mut arr = EslArray::new(vec!["last".into()]);
arr.unshift("middle".into());
arr.unshift("first".into());
assert_eq!(arr.items(), &["first", "middle", "last"]);
assert_eq!(arr.to_string(), "ARRAY::first|:middle|:last");
}
#[test]
fn parse_sip_uris_with_colons() {
let input = "ARRAY::sip:alice@atlanta.example.com|:sip:bob@biloxi.example.com";
let arr = EslArray::parse(input).unwrap();
assert_eq!(
arr.items(),
&[
"sip:alice@atlanta.example.com",
"sip:bob@biloxi.example.com"
]
);
assert_eq!(arr.to_string(), input);
}
#[test]
fn parse_sip_uris_with_angle_brackets_and_params() {
let input = "ARRAY::<sip:+15551234567@gw.example.com;user=phone>|:<tel:+15559876543>";
let arr = EslArray::parse(input).unwrap();
assert_eq!(arr.len(), 2);
assert_eq!(
arr.items()[0],
"<sip:+15551234567@gw.example.com;user=phone>"
);
assert_eq!(arr.items()[1], "<tel:+15559876543>");
assert_eq!(arr.to_string(), input);
}
}