#[cfg(feature = "internal")]
use alloc::vec::Vec;
use alloc::{
borrow::{Borrow, ToOwned},
format,
string::String,
};
use core::{
convert::{TryFrom, TryInto},
fmt,
ops::{Deref, Div},
};
use zenoh_result::{anyhow, bail, zerror, Error as ZError, ZResult};
use super::{canon::Canonize, OwnedKeyExpr, OwnedNonWildKeyExpr};
#[allow(non_camel_case_types)]
#[repr(transparent)]
#[derive(PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct keyexpr(str);
impl keyexpr {
pub fn new<'a, T, E>(t: &'a T) -> Result<&'a Self, E>
where
&'a Self: TryFrom<&'a T, Error = E>,
T: ?Sized,
{
t.try_into()
}
pub fn autocanonize<'a, T, E>(t: &'a mut T) -> Result<&'a Self, E>
where
&'a Self: TryFrom<&'a T, Error = E>,
T: Canonize + ?Sized,
{
t.canonize();
Self::new(t)
}
pub fn intersects(&self, other: &Self) -> bool {
use super::intersect::Intersector;
super::intersect::DEFAULT_INTERSECTOR.intersect(self, other)
}
pub fn includes(&self, other: &Self) -> bool {
use super::include::Includer;
super::include::DEFAULT_INCLUDER.includes(self, other)
}
#[cfg(feature = "unstable")]
pub fn relation_to(&self, other: &Self) -> SetIntersectionLevel {
use SetIntersectionLevel::*;
if self.intersects(other) {
if self == other {
Equals
} else if self.includes(other) {
Includes
} else {
Intersects
}
} else {
Disjoint
}
}
pub fn join<S: AsRef<str> + ?Sized>(&self, other: &S) -> ZResult<OwnedKeyExpr> {
OwnedKeyExpr::autocanonize(format!("{}/{}", self, other.as_ref()))
}
#[cfg(feature = "internal")]
#[doc(hidden)]
pub fn is_wild(&self) -> bool {
self.is_wild_impl()
}
pub(crate) fn is_wild_impl(&self) -> bool {
self.0.contains(super::SINGLE_WILD as char)
}
pub(crate) const fn is_double_wild(&self) -> bool {
let bytes = self.0.as_bytes();
bytes.len() == 2 && bytes[0] == b'*'
}
#[cfg(feature = "internal")]
#[doc(hidden)]
pub fn get_nonwild_prefix(&self) -> Option<&keyexpr> {
match self.0.find('*') {
Some(i) => match self.0[..i].rfind('/') {
Some(j) => unsafe { Some(keyexpr::from_str_unchecked(&self.0[..j])) },
None => None, },
None => Some(self), }
}
#[cfg(feature = "internal")]
#[doc(hidden)]
pub fn strip_prefix(&self, prefix: &Self) -> Vec<&keyexpr> {
let mut result = alloc::vec![];
'chunks: for i in (0..=self.len()).rev() {
if if i == self.len() {
self.ends_with("**")
} else {
self.as_bytes()[i] == b'/'
} {
let sub_part = keyexpr::new(&self[..i]).unwrap();
if sub_part.intersects(prefix) {
let remaining = if sub_part.ends_with("**") {
&self[i - 2..]
} else {
&self[i + 1..]
};
let remaining: &keyexpr = if remaining.is_empty() {
continue 'chunks;
} else {
remaining
}
.try_into()
.unwrap();
if remaining.as_bytes() == b"**" {
result.clear();
result.push(unsafe { keyexpr::from_str_unchecked(remaining) });
return result;
}
for i in (0..(result.len())).rev() {
if result[i].includes(remaining) {
continue 'chunks;
}
if remaining.includes(result[i]) {
result.swap_remove(i);
}
}
result.push(remaining);
}
}
}
result
}
#[cfg(feature = "internal")]
#[doc(hidden)]
pub fn strip_nonwild_prefix(&self, prefix: &nonwild_keyexpr) -> Option<&keyexpr> {
fn is_chunk_matching(target: &[u8], prefix: &[u8]) -> bool {
let mut target_idx: usize = 0;
let mut prefix_idx: usize = 0;
let mut target_prev: u8 = b'/';
if prefix.first() == Some(&b'@') && target.first() != Some(&b'@') {
return false;
}
while target_idx < target.len() && prefix_idx < prefix.len() {
if target[target_idx] == b'*' {
if target_prev == b'*' || target_idx + 1 == target.len() {
return true;
} else if target_prev == b'$' {
for i in prefix_idx..prefix.len() - 1 {
if is_chunk_matching(&target[target_idx + 1..], &prefix[i..]) {
return true;
}
}
}
} else if target[target_idx] == prefix[prefix_idx] {
prefix_idx += 1;
} else if target[target_idx] != b'$' {
return false;
}
target_prev = target[target_idx];
target_idx += 1;
}
if prefix_idx != prefix.len() {
return false;
}
target_idx == target.len()
|| (target_idx + 2 == target.len() && target[target_idx] == b'$')
}
fn strip_nonwild_prefix_inner<'a>(
target_bytes: &'a [u8],
prefix_bytes: &[u8],
) -> Option<&'a keyexpr> {
let mut target_idx = 0;
let mut prefix_idx = 0;
while target_idx < target_bytes.len() && prefix_idx < prefix_bytes.len() {
let target_end = target_idx
+ target_bytes[target_idx..]
.iter()
.position(|&i| i == b'/')
.unwrap_or(target_bytes.len() - target_idx);
let prefix_end = prefix_idx
+ prefix_bytes[prefix_idx..]
.iter()
.position(|&i| i == b'/')
.unwrap_or(prefix_bytes.len() - prefix_idx);
let target_chunk = &target_bytes[target_idx..target_end];
if target_chunk.len() == 2 && target_chunk[0] == b'*' {
let remaining_prefix = &prefix_bytes[prefix_idx..];
return match remaining_prefix.iter().position(|&x| x == b'@') {
Some(mut p) => {
if target_end + 1 >= target_bytes.len() {
return None;
} else {
loop {
if let Some(ke) = strip_nonwild_prefix_inner(
&target_bytes[(target_end + 1)..],
&remaining_prefix[p..],
) {
return Some(ke);
} else if p == 0 {
return None;
} else {
p -= 2;
while p > 0 && remaining_prefix[p - 1] != b'/' {
p -= 1;
}
}
}
}
}
None => unsafe {
Some(keyexpr::from_str_unchecked(core::str::from_utf8_unchecked(
&target_bytes[target_idx..],
)))
},
};
}
if target_end == target_bytes.len() {
return None;
}
let prefix_chunk = &prefix_bytes[prefix_idx..prefix_end];
if !is_chunk_matching(target_chunk, prefix_chunk) {
return None;
}
if prefix_end == prefix_bytes.len() {
return unsafe {
Some(keyexpr::from_str_unchecked(core::str::from_utf8_unchecked(
&target_bytes[(target_end + 1)..],
)))
};
}
target_idx = target_end + 1;
prefix_idx = prefix_end + 1;
}
None
}
let target_bytes = self.0.as_bytes();
let prefix_bytes = prefix.0.as_bytes();
strip_nonwild_prefix_inner(target_bytes, prefix_bytes)
}
pub const fn as_str(&self) -> &str {
&self.0
}
pub const unsafe fn from_str_unchecked(s: &str) -> &Self {
unsafe { core::mem::transmute(s) }
}
pub unsafe fn from_slice_unchecked(s: &[u8]) -> &Self {
unsafe { core::mem::transmute(s) }
}
#[cfg(feature = "internal")]
#[doc(hidden)]
pub const fn chunks(&self) -> Chunks<'_> {
self.chunks_impl()
}
pub(crate) const fn chunks_impl(&self) -> Chunks<'_> {
Chunks {
inner: self.as_str(),
}
}
pub(crate) fn next_delimiter(&self, i: usize) -> Option<usize> {
self.as_str()
.get(i + 1..)
.and_then(|s| s.find('/').map(|j| i + 1 + j))
}
pub(crate) fn previous_delimiter(&self, i: usize) -> Option<usize> {
self.as_str().get(..i).and_then(|s| s.rfind('/'))
}
pub(crate) fn first_byte(&self) -> u8 {
unsafe { *self.as_bytes().get_unchecked(0) }
}
pub(crate) fn iter_splits_ltr(&self) -> SplitsLeftToRight<'_> {
SplitsLeftToRight {
inner: self,
index: 0,
}
}
pub(crate) fn iter_splits_rtl(&self) -> SplitsRightToLeft<'_> {
SplitsRightToLeft {
inner: self,
index: self.len(),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub(crate) struct SplitsLeftToRight<'a> {
inner: &'a keyexpr,
index: usize,
}
impl<'a> SplitsLeftToRight<'a> {
fn right(&self) -> &'a str {
&self.inner[self.index + ((self.index != 0) as usize)..]
}
fn left(&self, followed_by_double: bool) -> &'a str {
&self.inner[..(self.index + ((self.index != 0) as usize + 2) * followed_by_double as usize)]
}
}
impl<'a> Iterator for SplitsLeftToRight<'a> {
type Item = (&'a keyexpr, &'a keyexpr);
fn next(&mut self) -> Option<Self::Item> {
match self.index < self.inner.len() {
false => None,
true => {
let right = self.right();
let double_wild = right.starts_with("**");
let left = self.left(double_wild);
self.index = if left.is_empty() {
self.inner.next_delimiter(0).unwrap_or(self.inner.len())
} else {
self.inner
.next_delimiter(left.len())
.unwrap_or(self.inner.len() + (left.len() == self.inner.len()) as usize)
};
if left.is_empty() {
self.next()
} else {
(!right.is_empty()).then(|| unsafe {
(
keyexpr::from_str_unchecked(left),
keyexpr::from_str_unchecked(right),
)
})
}
}
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub(crate) struct SplitsRightToLeft<'a> {
inner: &'a keyexpr,
index: usize,
}
impl<'a> SplitsRightToLeft<'a> {
fn right(&self, followed_by_double: bool) -> &'a str {
&self.inner[(self.index
- ((self.index != self.inner.len()) as usize + 2) * followed_by_double as usize)..]
}
fn left(&self) -> &'a str {
&self.inner[..(self.index - ((self.index != self.inner.len()) as usize))]
}
}
impl<'a> Iterator for SplitsRightToLeft<'a> {
type Item = (&'a keyexpr, &'a keyexpr);
fn next(&mut self) -> Option<Self::Item> {
match self.index {
0 => None,
_ => {
let left = self.left();
let double_wild = left.ends_with("**");
let right = self.right(double_wild);
self.index = if right.is_empty() {
self.inner
.previous_delimiter(self.inner.len())
.map_or(0, |n| n + 1)
} else {
self.inner
.previous_delimiter(
self.inner.len()
- right.len()
- (self.inner.len() != right.len()) as usize,
)
.map_or(0, |n| n + 1)
};
if right.is_empty() {
self.next()
} else {
(!left.is_empty()).then(|| unsafe {
(
keyexpr::from_str_unchecked(left),
keyexpr::from_str_unchecked(right),
)
})
}
}
}
}
}
#[test]
fn splits() {
let ke = keyexpr::new("a/**/b/c").unwrap();
let mut splits = ke.iter_splits_ltr();
assert_eq!(
splits.next(),
Some((
keyexpr::new("a/**").unwrap(),
keyexpr::new("**/b/c").unwrap()
))
);
assert_eq!(
splits.next(),
Some((keyexpr::new("a/**/b").unwrap(), keyexpr::new("c").unwrap()))
);
assert_eq!(splits.next(), None);
let mut splits = ke.iter_splits_rtl();
assert_eq!(
splits.next(),
Some((keyexpr::new("a/**/b").unwrap(), keyexpr::new("c").unwrap()))
);
assert_eq!(
splits.next(),
Some((
keyexpr::new("a/**").unwrap(),
keyexpr::new("**/b/c").unwrap()
))
);
assert_eq!(splits.next(), None);
let ke = keyexpr::new("**").unwrap();
let mut splits = ke.iter_splits_ltr();
assert_eq!(
splits.next(),
Some((keyexpr::new("**").unwrap(), keyexpr::new("**").unwrap()))
);
assert_eq!(splits.next(), None);
let ke = keyexpr::new("ab").unwrap();
let mut splits = ke.iter_splits_ltr();
assert_eq!(splits.next(), None);
let ke = keyexpr::new("ab/cd").unwrap();
let mut splits = ke.iter_splits_ltr();
assert_eq!(
splits.next(),
Some((keyexpr::new("ab").unwrap(), keyexpr::new("cd").unwrap()))
);
assert_eq!(splits.next(), None);
for (i, ke) in crate::fuzzer::KeyExprFuzzer(rand::thread_rng())
.take(100)
.enumerate()
{
dbg!(i, &ke);
let splits = ke.iter_splits_ltr().collect::<Vec<_>>();
assert_eq!(splits, {
let mut rtl_rev = ke.iter_splits_rtl().collect::<Vec<_>>();
rtl_rev.reverse();
rtl_rev
});
assert!(!splits
.iter()
.any(|s| s.0.ends_with('/') || s.1.starts_with('/')));
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Chunks<'a> {
inner: &'a str,
}
impl<'a> Chunks<'a> {
pub const fn as_keyexpr(self) -> Option<&'a keyexpr> {
match self.inner.is_empty() {
true => None,
_ => Some(unsafe { keyexpr::from_str_unchecked(self.inner) }),
}
}
pub fn peek(&self) -> Option<&keyexpr> {
if self.inner.is_empty() {
None
} else {
Some(unsafe {
keyexpr::from_str_unchecked(
&self.inner[..self.inner.find('/').unwrap_or(self.inner.len())],
)
})
}
}
pub fn peek_back(&self) -> Option<&keyexpr> {
if self.inner.is_empty() {
None
} else {
Some(unsafe {
keyexpr::from_str_unchecked(
&self.inner[self.inner.rfind('/').map_or(0, |i| i + 1)..],
)
})
}
}
}
impl<'a> Iterator for Chunks<'a> {
type Item = &'a keyexpr;
fn next(&mut self) -> Option<Self::Item> {
if self.inner.is_empty() {
return None;
}
let (next, inner) = self.inner.split_once('/').unwrap_or((self.inner, ""));
self.inner = inner;
Some(unsafe { keyexpr::from_str_unchecked(next) })
}
}
impl DoubleEndedIterator for Chunks<'_> {
fn next_back(&mut self) -> Option<Self::Item> {
if self.inner.is_empty() {
return None;
}
let (inner, next) = self.inner.rsplit_once('/').unwrap_or(("", self.inner));
self.inner = inner;
Some(unsafe { keyexpr::from_str_unchecked(next) })
}
}
impl Div for &keyexpr {
type Output = OwnedKeyExpr;
fn div(self, rhs: Self) -> Self::Output {
self.join(rhs).unwrap() }
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg(feature = "unstable")]
pub enum SetIntersectionLevel {
Disjoint,
Intersects,
Includes,
Equals,
}
#[cfg(feature = "unstable")]
#[test]
fn intersection_level_cmp() {
use SetIntersectionLevel::*;
assert!(Disjoint < Intersects);
assert!(Intersects < Includes);
assert!(Includes < Equals);
}
impl fmt::Debug for keyexpr {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "ke`{}`", self.as_ref())
}
}
impl fmt::Display for keyexpr {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self)
}
}
#[repr(i8)]
enum KeyExprError {
LoneDollarStar = -1,
SingleStarAfterDoubleStar = -2,
DoubleStarAfterDoubleStar = -3,
EmptyChunk = -4,
StarInChunk = -5,
DollarAfterDollar = -6,
SharpOrQMark = -7,
UnboundDollar = -8,
}
impl KeyExprError {
#[cold]
fn into_err(self, s: &str) -> ZError {
let error = match &self {
Self::LoneDollarStar => anyhow!("Invalid Key Expr `{s}`: empty chunks are forbidden, as well as leading and trailing slashes"),
Self::SingleStarAfterDoubleStar => anyhow!("Invalid Key Expr `{s}`: `**/*` must be replaced by `*/**` to reach canon-form"),
Self::DoubleStarAfterDoubleStar => anyhow!("Invalid Key Expr `{s}`: `**/**` must be replaced by `**` to reach canon-form"),
Self::EmptyChunk => anyhow!("Invalid Key Expr `{s}`: empty chunks are forbidden, as well as leading and trailing slashes"),
Self::StarInChunk => anyhow!("Invalid Key Expr `{s}`: `*` may only be preceded by `/` or `$`"),
Self::DollarAfterDollar => anyhow!("Invalid Key Expr `{s}`: `$` is not allowed after `$*`"),
Self::SharpOrQMark => anyhow!("Invalid Key Expr `{s}`: `#` and `?` are forbidden characters"),
Self::UnboundDollar => anyhow!("Invalid Key Expr `{s}`: `$` is only allowed in `$*`")
};
zerror!((self) error).into()
}
}
impl<'a> TryFrom<&'a str> for &'a keyexpr {
type Error = ZError;
fn try_from(value: &'a str) -> Result<Self, Self::Error> {
use KeyExprError::*;
if value.is_empty() || value.ends_with('/') {
return Err(EmptyChunk.into_err(value));
}
let bytes = value.as_bytes();
let mut chunk_start = 0;
let mut i = 0;
while i < bytes.len() {
match bytes[i] {
c if c > b'/' && c != b'?' => i += 1,
b'/' if i == chunk_start => return Err(EmptyChunk.into_err(value)),
b'/' => {
i += 1;
chunk_start = i;
}
b'*' if i != chunk_start => return Err(StarInChunk.into_err(value)),
b'*' => match bytes.get(i + 1) {
None => break,
Some(&b'/') => {
i += 2;
chunk_start = i;
}
Some(&b'*') => match bytes.get(i + 2) {
None => break,
Some(&b'/') if matches!(bytes.get(i + 3), Some(b'*')) => {
#[cold]
fn double_star_err(value: &str, i: usize) -> ZError {
match (value.as_bytes().get(i + 4), value.as_bytes().get(i + 5)) {
(None | Some(&b'/'), _) => SingleStarAfterDoubleStar,
(Some(&b'*'), None | Some(&b'/')) => DoubleStarAfterDoubleStar,
_ => StarInChunk,
}
.into_err(value)
}
return Err(double_star_err(value, i));
}
Some(&b'/') => {
i += 3;
chunk_start = i;
}
_ => return Err(StarInChunk.into_err(value)),
},
_ => return Err(StarInChunk.into_err(value)),
},
b'$' if bytes.get(i + 1) != Some(&b'*') => {
return Err(UnboundDollar.into_err(value))
}
b'$' => match bytes.get(i + 2) {
Some(&b'$') => return Err(DollarAfterDollar.into_err(value)),
Some(&b'/') | None if i == chunk_start => {
return Err(LoneDollarStar.into_err(value))
}
None => break,
_ => i += 2,
},
b'#' | b'?' => return Err(SharpOrQMark.into_err(value)),
_ => i += 1,
}
}
Ok(unsafe { keyexpr::from_str_unchecked(value) })
}
}
impl<'a> TryFrom<&'a mut str> for &'a keyexpr {
type Error = ZError;
fn try_from(value: &'a mut str) -> Result<Self, Self::Error> {
(value as &'a str).try_into()
}
}
impl<'a> TryFrom<&'a mut String> for &'a keyexpr {
type Error = ZError;
fn try_from(value: &'a mut String) -> Result<Self, Self::Error> {
(value.as_str()).try_into()
}
}
impl<'a> TryFrom<&'a String> for &'a keyexpr {
type Error = ZError;
fn try_from(value: &'a String) -> Result<Self, Self::Error> {
(value.as_str()).try_into()
}
}
impl<'a> TryFrom<&'a &'a str> for &'a keyexpr {
type Error = ZError;
fn try_from(value: &'a &'a str) -> Result<Self, Self::Error> {
(*value).try_into()
}
}
impl<'a> TryFrom<&'a &'a mut str> for &'a keyexpr {
type Error = ZError;
fn try_from(value: &'a &'a mut str) -> Result<Self, Self::Error> {
keyexpr::new(*value)
}
}
#[test]
fn autocanon() {
let mut s: Box<str> = Box::from("hello/**/*");
let mut s: &mut str = &mut s;
assert_eq!(keyexpr::autocanonize(&mut s).unwrap(), "hello/*/**");
}
impl Deref for keyexpr {
type Target = str;
fn deref(&self) -> &Self::Target {
unsafe { core::mem::transmute(self) }
}
}
impl AsRef<str> for keyexpr {
fn as_ref(&self) -> &str {
self
}
}
impl PartialEq<str> for keyexpr {
fn eq(&self, other: &str) -> bool {
self.as_str() == other
}
}
impl PartialEq<keyexpr> for str {
fn eq(&self, other: &keyexpr) -> bool {
self == other.as_str()
}
}
impl Borrow<keyexpr> for OwnedKeyExpr {
fn borrow(&self) -> &keyexpr {
self
}
}
impl ToOwned for keyexpr {
type Owned = OwnedKeyExpr;
fn to_owned(&self) -> Self::Owned {
OwnedKeyExpr::from(self)
}
}
#[allow(non_camel_case_types)]
#[repr(transparent)]
#[derive(PartialEq, Eq, Hash, PartialOrd, Ord, Debug)]
pub struct nonwild_keyexpr(keyexpr);
impl nonwild_keyexpr {
pub fn new<'a, T, E>(t: &'a T) -> Result<&'a Self, ZError>
where
&'a keyexpr: TryFrom<&'a T, Error = E>,
E: Into<ZError>,
T: ?Sized,
{
let ke: &'a keyexpr = t.try_into().map_err(|e: E| e.into())?;
ke.try_into()
}
pub const unsafe fn from_str_unchecked(s: &str) -> &Self {
unsafe { core::mem::transmute(s) }
}
}
impl Deref for nonwild_keyexpr {
type Target = keyexpr;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<'a> TryFrom<&'a keyexpr> for &'a nonwild_keyexpr {
type Error = ZError;
fn try_from(value: &'a keyexpr) -> Result<Self, Self::Error> {
if value.is_wild_impl() {
bail!("nonwild_keyexpr can not contain any wild chunks")
}
Ok(unsafe { core::mem::transmute::<&keyexpr, &nonwild_keyexpr>(value) })
}
}
impl Borrow<nonwild_keyexpr> for OwnedNonWildKeyExpr {
fn borrow(&self) -> &nonwild_keyexpr {
self
}
}
impl ToOwned for nonwild_keyexpr {
type Owned = OwnedNonWildKeyExpr;
fn to_owned(&self) -> Self::Owned {
OwnedNonWildKeyExpr::from(self)
}
}
#[cfg(feature = "internal")]
#[test]
fn test_keyexpr_strip_prefix() {
let expectations = [
(("demo/example/test/**", "demo/example/test"), &["**"][..]),
(("demo/example/**", "demo/example/test"), &["**"]),
(("**", "demo/example/test"), &["**"]),
(
("demo/example/test/**/x$*/**", "demo/example/test"),
&["**/x$*/**"],
),
(("demo/**/xyz", "demo/example/test"), &["**/xyz"]),
(("demo/**/test/**", "demo/example/test"), &["**"]),
(
("demo/**/ex$*/*/xyz", "demo/example/test"),
["xyz", "**/ex$*/*/xyz"].as_ref(),
),
(
("demo/**/ex$*/t$*/xyz", "demo/example/test"),
["xyz", "**/ex$*/t$*/xyz"].as_ref(),
),
(
("demo/**/te$*/*/xyz", "demo/example/test"),
["*/xyz", "**/te$*/*/xyz"].as_ref(),
),
(("demo/example/test", "demo/example/test"), [].as_ref()),
]
.map(|((a, b), expected)| {
(
(keyexpr::new(a).unwrap(), keyexpr::new(b).unwrap()),
expected
.iter()
.map(|s| keyexpr::new(*s).unwrap())
.collect::<Vec<_>>(),
)
});
for ((ke, prefix), expected) in expectations {
dbg!(ke, prefix);
assert_eq!(ke.strip_prefix(prefix), expected)
}
}
#[cfg(feature = "internal")]
#[test]
fn test_keyexpr_strip_nonwild_prefix() {
let expectations = [
(("demo/example/test/**", "demo/example/test"), Some("**")),
(("demo/example/**", "demo/example/test"), Some("**")),
(("**", "demo/example/test"), Some("**")),
(("*/example/test/1", "demo/example/test"), Some("1")),
(("demo/*/test/1", "demo/example/test"), Some("1")),
(("*/*/test/1", "demo/example/test"), Some("1")),
(("*/*/*/1", "demo/example/test"), Some("1")),
(("*/test/1", "demo/example/test"), None),
(("*/*/1", "demo/example/test"), None),
(("*/*/**", "demo/example/test"), Some("**")),
(
("demo/example/test/**/x$*/**", "demo/example/test"),
Some("**/x$*/**"),
),
(("demo/**/xyz", "demo/example/test"), Some("**/xyz")),
(("demo/**/test/**", "demo/example/test"), Some("**/test/**")),
(
("demo/**/ex$*/*/xyz", "demo/example/test"),
Some("**/ex$*/*/xyz"),
),
(
("demo/**/ex$*/t$*/xyz", "demo/example/test"),
Some("**/ex$*/t$*/xyz"),
),
(
("demo/**/te$*/*/xyz", "demo/example/test"),
Some("**/te$*/*/xyz"),
),
(("demo/example/test", "demo/example/test"), None),
(("demo/example/test1/something", "demo/example/test"), None),
(
("demo/example/test$*/something", "demo/example/test"),
Some("something"),
),
(("*/example/test/something", "@demo/example/test"), None),
(("**/test/something", "@demo/example/test"), None),
(("**/test/something", "demo/example/@test"), None),
(
("@demo/*/test/something", "@demo/example/test"),
Some("something"),
),
(("@demo/*/test/something", "@demo/@example/test"), None),
(("**/@demo/test/something", "@demo/test"), Some("something")),
(("**/@test/something", "demo/@test"), Some("something")),
(
("@demo/**/@test/something", "@demo/example/@test"),
Some("something"),
),
]
.map(|((a, b), expected)| {
(
(keyexpr::new(a).unwrap(), nonwild_keyexpr::new(b).unwrap()),
expected.map(|t| keyexpr::new(t).unwrap()),
)
});
for ((ke, prefix), expected) in expectations {
dbg!(ke, prefix);
assert_eq!(ke.strip_nonwild_prefix(prefix), expected)
}
}
#[cfg(test)]
mod tests {
use test_case::test_case;
use zenoh_result::ErrNo;
use crate::{
key_expr::borrowed::{KeyExprError, KeyExprError::*},
keyexpr,
};
#[test_case("", EmptyChunk; "Empty")]
#[test_case("demo/example/test", None; "Normal key_expr")]
#[test_case("demo/*", None; "Single star at the end")]
#[test_case("demo/**", None; "Double star at the end")]
#[test_case("demo/*/*/test", None; "Single star after single star")]
#[test_case("demo/*/**/test", None; "Double star after single star")]
#[test_case("demo/example$*/test", None; "Dollar with star")]
#[test_case("demo/example$*-$*/test", None; "Multiple dollar with star")]
#[test_case("/demo/example/test", EmptyChunk; "Leading /")]
#[test_case("demo/example/test/", EmptyChunk; "Trailing /")]
#[test_case("demo/$*/test", LoneDollarStar; "Lone $*")]
#[test_case("demo/$*", LoneDollarStar; "Lone $* at the end")]
#[test_case("demo/example$*", None; "Trailing lone $*")]
#[test_case("demo/**/*/test", SingleStarAfterDoubleStar; "Single star after double star")]
#[test_case("demo/**/**/test", DoubleStarAfterDoubleStar; "Double star after double star")]
#[test_case("demo//test", EmptyChunk; "Empty Chunk")]
#[test_case("demo/exam*ple/test", StarInChunk; "Star in chunk")]
#[test_case("demo/example$*$/test", DollarAfterDollar; "Dollar after dollar or star")]
#[test_case("demo/example$*$*/test", DollarAfterDollar; "Dollar star after dollar or star")]
#[test_case("demo/example#/test", SharpOrQMark; "Contain sharp")]
#[test_case("demo/example?/test", SharpOrQMark; "Contain mark")]
#[test_case("demo/$/test", UnboundDollar; "Contain unbounded dollar")]
fn test_keyexpr_new(key_str: &str, error: impl Into<Option<KeyExprError>>) {
assert_eq!(
keyexpr::new(key_str).err().map(|err| {
err.downcast_ref::<zenoh_result::ZError>()
.unwrap()
.errno()
.get()
}),
error.into().map(|err| err as i8)
);
}
}