use crate::parser::{ParseError, SubtagIterator};
use crate::shortvec::ShortBoxSlice;
use crate::subtags::{subtag, Subtag};
use core::ops::RangeInclusive;
use core::str::FromStr;
#[derive(Debug, PartialEq, Eq, Clone, Hash, PartialOrd, Ord, Default)]
pub struct Value(ShortBoxSlice<Subtag>);
const TYPE_LENGTH: RangeInclusive<usize> = 3..=8;
const TRUE_TVALUE: Subtag = subtag!("true");
impl Value {
#[inline]
pub fn try_from_str(s: &str) -> Result<Self, ParseError> {
Self::try_from_utf8(s.as_bytes())
}
pub fn try_from_utf8(code_units: &[u8]) -> Result<Self, ParseError> {
let mut v = ShortBoxSlice::default();
let mut has_value = false;
for subtag in SubtagIterator::new(code_units) {
if !Self::is_type_subtag(subtag) {
return Err(ParseError::InvalidExtension);
}
has_value = true;
let val = Subtag::try_from_utf8(subtag).map_err(|_| ParseError::InvalidExtension)?;
if val != TRUE_TVALUE {
v.push(val);
}
}
if !has_value {
return Err(ParseError::InvalidExtension);
}
Ok(Self(v))
}
pub(crate) fn from_short_slice_unchecked(input: ShortBoxSlice<Subtag>) -> Self {
Self(input)
}
pub(crate) fn is_type_subtag(t: &[u8]) -> bool {
TYPE_LENGTH.contains(&t.len()) && t.iter().all(u8::is_ascii_alphanumeric)
}
pub(crate) fn parse_subtag(t: &[u8]) -> Result<Option<Subtag>, ParseError> {
if !TYPE_LENGTH.contains(&t.len()) {
return Err(ParseError::InvalidExtension);
}
let s = Subtag::try_from_utf8(t).map_err(|_| ParseError::InvalidSubtag)?;
let s = s.to_ascii_lowercase();
if s == TRUE_TVALUE {
Ok(None)
} else {
Ok(Some(s))
}
}
pub(crate) fn for_each_subtag_str<E, F>(&self, f: &mut F) -> Result<(), E>
where
F: FnMut(&str) -> Result<(), E>,
{
if self.0.is_empty() {
f(TRUE_TVALUE.as_str())?;
} else {
self.0.iter().map(Subtag::as_str).try_for_each(f)?;
}
Ok(())
}
}
impl FromStr for Value {
type Err = ParseError;
#[inline]
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::try_from_str(s)
}
}
impl_writeable_for_each_subtag_str_no_test!(Value, selff, selff.0.is_empty() => alloc::borrow::Cow::Borrowed("true"));
#[test]
fn test_writeable() {
use writeable::assert_writeable_eq;
let hybrid = "hybrid".parse().unwrap();
let foobar = "foobar".parse().unwrap();
assert_writeable_eq!(Value::default(), "true");
assert_writeable_eq!(
Value::from_short_slice_unchecked(vec![hybrid].into()),
"hybrid"
);
assert_writeable_eq!(
Value::from_short_slice_unchecked(vec![hybrid, foobar].into()),
"hybrid-foobar"
);
}
#[test]
fn test_short_tvalue() {
let value = Value::try_from_str("foo-longstag");
assert!(value.is_ok());
let value = value.unwrap();
assert_eq!(value.0.len(), 2);
for (s, reference) in value.0.iter().zip(&[subtag!("foo"), subtag!("longstag")]) {
assert_eq!(s, reference);
}
let value = Value::try_from_str("foo-ba");
assert!(value.is_err());
}