use crate::helpers::ShortVec;
use crate::parser::{ParserError, SubtagIterator};
use alloc::vec::Vec;
use core::ops::RangeInclusive;
use core::str::FromStr;
use tinystr::TinyAsciiStr;
#[derive(Debug, PartialEq, Eq, Clone, Hash, PartialOrd, Ord, Default)]
pub struct Value(ShortVec<TinyAsciiStr<{ *VALUE_LENGTH.end() }>>);
const VALUE_LENGTH: RangeInclusive<usize> = 3..=8;
const TRUE_VALUE: TinyAsciiStr<8> = tinystr::tinystr!(8, "true");
impl Value {
pub fn try_from_bytes(input: &[u8]) -> Result<Self, ParserError> {
let mut v = ShortVec::new();
if !input.is_empty() {
for subtag in SubtagIterator::new(input) {
let val = Self::subtag_from_bytes(subtag)?;
if let Some(val) = val {
v.push(val);
}
}
}
Ok(Self(v))
}
pub const fn try_from_single_subtag(subtag: &[u8]) -> Result<Self, ParserError> {
match Self::subtag_from_bytes(subtag) {
Err(_) => Err(ParserError::InvalidExtension),
Ok(option) => Ok(Self::from_tinystr(option)),
}
}
#[doc(hidden)]
pub fn as_tinystr_slice(&self) -> &[TinyAsciiStr<8>] {
self.0.as_slice()
}
#[doc(hidden)]
pub const fn as_single_subtag(&self) -> Option<&TinyAsciiStr<8>> {
self.0.single()
}
#[doc(hidden)]
pub const fn from_tinystr(subtag: Option<TinyAsciiStr<8>>) -> Self {
match subtag {
None => Self(ShortVec::new()),
Some(val) => {
debug_assert!(val.is_ascii_alphanumeric());
debug_assert!(!matches!(val, TRUE_VALUE));
Self(ShortVec::new_single(val))
}
}
}
pub(crate) fn from_vec_unchecked(input: Vec<TinyAsciiStr<8>>) -> Self {
Self(input.into())
}
#[doc(hidden)]
pub const fn subtag_from_bytes(bytes: &[u8]) -> Result<Option<TinyAsciiStr<8>>, ParserError> {
Self::parse_subtag_from_bytes_manual_slice(bytes, 0, bytes.len())
}
pub(crate) fn parse_subtag(t: &[u8]) -> Result<Option<TinyAsciiStr<8>>, ParserError> {
Self::parse_subtag_from_bytes_manual_slice(t, 0, t.len())
}
pub(crate) const fn parse_subtag_from_bytes_manual_slice(
bytes: &[u8],
start: usize,
end: usize,
) -> Result<Option<TinyAsciiStr<8>>, ParserError> {
let slice_len = end - start;
if slice_len > *VALUE_LENGTH.end() || slice_len < *VALUE_LENGTH.start() {
return Err(ParserError::InvalidExtension);
}
match TinyAsciiStr::from_bytes_manual_slice(bytes, start, end) {
Ok(TRUE_VALUE) => Ok(None),
Ok(s) if s.is_ascii_alphanumeric() => Ok(Some(s.to_ascii_lowercase())),
Ok(_) => Err(ParserError::InvalidExtension),
Err(_) => Err(ParserError::InvalidSubtag),
}
}
pub(crate) fn for_each_subtag_str<E, F>(&self, f: &mut F) -> Result<(), E>
where
F: FnMut(&str) -> Result<(), E>,
{
self.0.as_slice().iter().map(|t| t.as_str()).try_for_each(f)
}
}
impl FromStr for Value {
type Err = ParserError;
fn from_str(source: &str) -> Result<Self, Self::Err> {
Self::try_from_bytes(source.as_bytes())
}
}
impl_writeable_for_subtag_list!(Value, "islamic", "civil");
#[macro_export]
macro_rules! extensions_unicode_value {
($value:literal) => {{
const R: $crate::extensions::unicode::Value =
$crate::extensions::unicode::Value::from_tinystr(
match $crate::extensions::unicode::Value::subtag_from_bytes($value.as_bytes()) {
Ok(r) => r,
_ => panic!(concat!("Invalid Unicode extension value: ", $value)),
},
);
R
}};
}