#[cfg(feature = "alloc")]
use alloc::{borrow::Cow, boxed::Box, collections::BTreeMap, string::String};
use core::fmt;
#[cfg(feature = "alloc")]
use core::str::FromStr;
#[cfg(feature = "litemap")]
use litemap::LiteMap;
use writeable::Writeable;
use crate::common::*;
use crate::Error;
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
#[repr(transparent)]
#[allow(clippy::exhaustive_structs)] pub struct MultiNamedPlaceholderKey<'a>(pub &'a str);
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[repr(transparent)]
#[allow(clippy::exhaustive_structs)] #[cfg(feature = "alloc")]
pub struct MultiNamedPlaceholderKeyCow<'a>(pub Cow<'a, str>);
#[cfg(feature = "alloc")]
impl FromStr for MultiNamedPlaceholderKeyCow<'_> {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(MultiNamedPlaceholderKeyCow(Cow::Owned(String::from(s))))
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct MissingNamedPlaceholderError<'a> {
pub name: &'a str,
}
impl Writeable for MissingNamedPlaceholderError<'_> {
fn write_to<W: fmt::Write + ?Sized>(&self, sink: &mut W) -> fmt::Result {
sink.write_char('{')?;
sink.write_str(self.name)?;
sink.write_char('}')?;
Ok(())
}
}
#[cfg(feature = "alloc")]
impl<'k, K, W> PlaceholderValueProvider<MultiNamedPlaceholderKey<'k>> for BTreeMap<K, W>
where
K: Ord + core::borrow::Borrow<str>,
W: Writeable,
{
type Error = MissingNamedPlaceholderError<'k>;
type W<'a>
= Result<&'a W, Self::Error>
where
Self: 'a;
type L<'a, 'l>
= &'l str
where
Self: 'a;
#[inline]
fn value_for<'a>(&'a self, key: MultiNamedPlaceholderKey<'k>) -> Self::W<'a> {
match self.get(key.0) {
Some(value) => Ok(value),
None => Err(MissingNamedPlaceholderError { name: key.0 }),
}
}
#[inline]
fn map_literal<'a, 'l>(&'a self, literal: &'l str) -> Self::L<'a, 'l> {
literal
}
}
#[cfg(feature = "litemap")]
impl<'k, K, W, S> PlaceholderValueProvider<MultiNamedPlaceholderKey<'k>> for LiteMap<K, W, S>
where
K: Ord + core::borrow::Borrow<str>,
W: Writeable,
S: litemap::store::Store<K, W>,
{
type Error = MissingNamedPlaceholderError<'k>;
type W<'a>
= Result<&'a W, Self::Error>
where
Self: 'a;
type L<'a, 'l>
= &'l str
where
Self: 'a;
#[inline]
fn value_for<'a>(&'a self, key: MultiNamedPlaceholderKey<'k>) -> Self::W<'a> {
match self.get(key.0) {
Some(value) => Ok(value),
None => Err(MissingNamedPlaceholderError { name: key.0 }),
}
}
#[inline]
fn map_literal<'a, 'l>(&'a self, literal: &'l str) -> Self::L<'a, 'l> {
literal
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[allow(clippy::exhaustive_enums)] pub enum MultiNamedPlaceholder {}
impl crate::private::Sealed for MultiNamedPlaceholder {}
impl PatternBackend for MultiNamedPlaceholder {
type PlaceholderKey<'a> = MultiNamedPlaceholderKey<'a>;
#[cfg(feature = "alloc")]
type PlaceholderKeyCow<'a> = MultiNamedPlaceholderKeyCow<'a>;
type Error<'a> = MissingNamedPlaceholderError<'a>;
type Store = str;
type Iter<'a> = MultiNamedPlaceholderPatternIterator<'a>;
fn validate_store(store: &Self::Store) -> Result<(), Error> {
let mut iter = MultiNamedPlaceholderPatternIterator::new(store);
while iter
.try_next()
.map_err(|e| match e {
MultiNamedPlaceholderError::InvalidStore => Error::InvalidPattern,
MultiNamedPlaceholderError::Unreachable => {
debug_assert!(false, "unreachable");
Error::InvalidPattern
}
})?
.is_some()
{}
Ok(())
}
fn iter_items(store: &Self::Store) -> Self::Iter<'_> {
MultiNamedPlaceholderPatternIterator::new(store)
}
#[cfg(feature = "alloc")]
fn try_from_items<
'cow,
'ph,
I: Iterator<Item = Result<PatternItemCow<'cow, Self::PlaceholderKeyCow<'ph>>, Error>>,
>(
items: I,
) -> Result<Box<str>, Error> {
let mut string = String::new();
for item in items {
match item? {
PatternItemCow::Literal(s) if s.contains(|x| (x as usize) <= 0x07) => {
return Err(Error::InvalidPattern);
}
PatternItemCow::Literal(s) => string.push_str(&s),
PatternItemCow::Placeholder(ph_key) => {
let name_length = ph_key.0.len();
if name_length >= 64 {
return Err(Error::InvalidPlaceholder);
}
let lead = (name_length >> 3) as u8;
let trail = (name_length & 0x7) as u8;
string.push(char::from(lead));
string.push(char::from(trail));
string.push_str(&ph_key.0);
}
}
}
Ok(string.into_boxed_str())
}
fn empty() -> &'static Self::Store {
""
}
}
#[derive(Debug)]
pub struct MultiNamedPlaceholderPatternIterator<'a> {
store: &'a str,
}
impl<'a> Iterator for MultiNamedPlaceholderPatternIterator<'a> {
type Item = PatternItem<'a, MultiNamedPlaceholderKey<'a>>;
fn next(&mut self) -> Option<Self::Item> {
match self.try_next() {
Ok(next) => next,
Err(MultiNamedPlaceholderError::InvalidStore) => {
debug_assert!(
false,
"invalid store with {} bytes remaining",
self.store.len()
);
None
}
Err(MultiNamedPlaceholderError::Unreachable) => {
debug_assert!(false, "unreachable");
None
}
}
}
}
enum MultiNamedPlaceholderError {
InvalidStore,
Unreachable,
}
impl<'a> MultiNamedPlaceholderPatternIterator<'a> {
fn new(store: &'a str) -> Self {
Self { store }
}
fn try_next(
&mut self,
) -> Result<Option<PatternItem<'a, MultiNamedPlaceholderKey<'a>>>, MultiNamedPlaceholderError>
{
match self.store.find(|x| (x as usize) <= 0x07) {
Some(0) => {
let Some((&[lead, trail], remainder)) = self
.store
.split_at_checked(2)
.map(|(a, b)| (a.as_bytes(), b))
else {
return Err(MultiNamedPlaceholderError::InvalidStore);
};
debug_assert!(lead <= 7);
if trail > 7 {
return Err(MultiNamedPlaceholderError::InvalidStore);
}
let placeholder_len = (lead << 3) + trail;
let Some((placeholder_name, remainder)) =
remainder.split_at_checked(placeholder_len as usize)
else {
return Err(MultiNamedPlaceholderError::InvalidStore);
};
self.store = remainder;
Ok(Some(PatternItem::Placeholder(MultiNamedPlaceholderKey(
placeholder_name,
))))
}
Some(i) => {
let Some((literal, remainder)) = self.store.split_at_checked(i) else {
debug_assert!(false, "should be a perfect slice");
return Err(MultiNamedPlaceholderError::Unreachable);
};
self.store = remainder;
Ok(Some(PatternItem::Literal(literal)))
}
None if self.store.is_empty() => {
Ok(None)
}
None => {
let literal = self.store;
self.store = "";
Ok(Some(PatternItem::Literal(literal)))
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{MultiNamedPlaceholder, MultiNamedPlaceholderPattern};
#[test]
fn test_invalid() {
let long_str = "0123456789".repeat(1000000);
let strings = [
"{", "{@}", "\x00", "\x07", ];
for string in strings {
let string = string.replace('@', &long_str);
assert!(
MultiNamedPlaceholderPattern::try_from_str(&string, Default::default()).is_err(),
"{string:?}"
);
}
let stores = [
"\x00", "\x02", "\x00\x02", "\x00\x02a", ];
for store in stores {
assert!(
MultiNamedPlaceholder::validate_store(store).is_err(),
"{store:?}"
);
}
}
}