use crate::FieldValueError;
use std::iter::FusedIterator;
pub fn split_list(value: &[u8]) -> ListElements<'_> {
ListElements { rest: Some(value) }
}
#[derive(Clone, Debug)]
pub struct ListElements<'a> {
rest: Option<&'a [u8]>,
}
impl<'a> Iterator for ListElements<'a> {
type Item = Result<&'a [u8], FieldValueError>;
fn next(&mut self) -> Option<Self::Item> {
while let Some(rest) = self.rest {
let (mut quoted, mut escaped, mut separator) = (false, false, None);
for (index, &byte) in rest.iter().enumerate() {
if escaped {
escaped = false;
} else if quoted && byte == b'\\' {
escaped = true;
} else if byte == b'"' {
quoted = !quoted;
} else if byte == b',' && !quoted {
separator = Some(index);
break;
}
}
let element = match separator {
Some(index) => {
self.rest = Some(&rest[index + 1..]);
&rest[..index]
}
None => {
self.rest = None;
if quoted {
return Some(Err(FieldValueError::new("unterminated quoted string")));
}
rest
}
};
let element = trim_whitespace(element);
if !element.is_empty() {
return Some(Ok(element));
}
}
None
}
}
impl FusedIterator for ListElements<'_> {}
pub fn trim_whitespace(value: &[u8]) -> &[u8] {
let start = value
.iter()
.position(|byte| !matches!(byte, b' ' | b'\t'))
.unwrap_or(value.len());
let end = value
.iter()
.rposition(|byte| !matches!(byte, b' ' | b'\t'))
.map_or(start, |index| index + 1);
&value[start..end]
}
pub fn ascii_text(value: &[u8]) -> Result<&str, FieldValueError> {
let invalid = FieldValueError::new("value is not visible ASCII");
let text = std::str::from_utf8(value).map_err(|_| invalid)?;
if !text
.bytes()
.all(|byte| byte.is_ascii_graphic() || byte == b' ' || byte == b'\t')
{
return Err(invalid);
}
Ok(text)
}