Skip to main content

zenoh_keyexpr/key_expr/
borrowed.rs

1//
2// Copyright (c) 2023 ZettaScale Technology
3//
4// This program and the accompanying materials are made available under the
5// terms of the Eclipse Public License 2.0 which is available at
6// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
7// which is available at https://www.apache.org/licenses/LICENSE-2.0.
8//
9// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
10//
11// Contributors:
12//   ZettaScale Zenoh Team, <zenoh@zettascale.tech>
13//
14
15#[cfg(feature = "internal")]
16use alloc::vec::Vec;
17use alloc::{
18    borrow::{Borrow, ToOwned},
19    format,
20    string::String,
21};
22use core::{
23    convert::{TryFrom, TryInto},
24    fmt,
25    ops::{Deref, Div},
26};
27
28use zenoh_result::{anyhow, bail, zerror, Error as ZError, ZResult};
29
30use super::{canon::Canonize, OwnedKeyExpr, OwnedNonWildKeyExpr};
31
32/// A [`str`] newtype that is statically known to be a valid key expression.
33///
34/// The exact key expression specification can be found [here](https://github.com/eclipse-zenoh/roadmap/blob/main/rfcs/ALL/Key%20Expressions.md). Here are the major lines:
35/// * Key expressions are conceptually a `/`-separated list of UTF-8 string typed chunks. These chunks are not allowed to be empty.
36/// * Key expressions must be valid UTF-8 strings.
37///   Be aware that Zenoh does not perform UTF normalization for you, so get familiar with that concept if your key expression contains glyphs that may have several unicode representation, such as accented characters.
38/// * Key expressions may never start or end with `'/'`, nor contain `"//"` or any of the following characters: `#$?`
39/// * Key expression must be in canon-form (this ensure that key expressions representing the same set are always the same string).
40///   Note that safe constructors will perform canonization for you if this can be done without extraneous allocations.
41///
42/// Since Key Expressions define sets of keys, you may want to be aware of the hierarchy of [relations](keyexpr::relation_to) between such sets:
43/// * Trivially, two sets can have no elements in common: `a/**` and `b/**` for example define two disjoint sets of keys.
44/// * Two sets [intersect](keyexpr::intersects()) if they have at least one element in common. `a/*` intersects `*/a` on `a/a` for example.
45/// * One set A [includes](keyexpr::includes()) the other set B if all of B's elements are in A: `a/*/**` includes `a/b/**`
46/// * Two sets A and B are equal if all A includes B and B includes A. The Key Expression language is designed so that string equality is equivalent to set equality.
47#[allow(non_camel_case_types)]
48#[repr(transparent)]
49#[derive(PartialEq, Eq, Hash, PartialOrd, Ord)]
50pub struct keyexpr(str);
51
52impl keyexpr {
53    /// Equivalent to `<&keyexpr as TryFrom>::try_from(t)`.
54    ///
55    /// Will return an Err if `t` isn't a valid key expression.
56    /// Note that to be considered a valid key expression, a string MUST be canon.
57    ///
58    /// [`keyexpr::autocanonize`] is an alternative constructor that will canonize the passed expression before constructing it.
59    pub fn new<'a, T, E>(t: &'a T) -> Result<&'a Self, E>
60    where
61        &'a Self: TryFrom<&'a T, Error = E>,
62        T: ?Sized,
63    {
64        t.try_into()
65    }
66
67    /// Canonizes the passed value before returning it as a `&keyexpr`.
68    ///
69    /// Will return Err if the passed value isn't a valid key expression despite canonization.
70    ///
71    /// Note that this function does not allocate, and will instead mutate the passed value in place during canonization.
72    pub fn autocanonize<'a, T, E>(t: &'a mut T) -> Result<&'a Self, E>
73    where
74        &'a Self: TryFrom<&'a T, Error = E>,
75        T: Canonize + ?Sized,
76    {
77        t.canonize();
78        Self::new(t)
79    }
80
81    /// Returns `true` if the `keyexpr`s intersect, i.e. there exists at least one key which is contained in both of the sets defined by `self` and `other`.
82    pub fn intersects(&self, other: &Self) -> bool {
83        use super::intersect::Intersector;
84        super::intersect::DEFAULT_INTERSECTOR.intersect(self, other)
85    }
86
87    /// Returns `true` if `self` includes `other`, i.e. the set defined by `self` contains every key belonging to the set defined by `other`.
88    pub fn includes(&self, other: &Self) -> bool {
89        use super::include::Includer;
90        super::include::DEFAULT_INCLUDER.includes(self, other)
91    }
92
93    /// Returns the relation between `self` and `other` from `self`'s point of view ([`SetIntersectionLevel::Includes`] signifies that `self` includes `other`).
94    ///
95    /// Note that this is slower than [`keyexpr::intersects`] and [`keyexpr::includes`], so you should favor these methods for most applications.
96    #[cfg(feature = "unstable")]
97    pub fn relation_to(&self, other: &Self) -> SetIntersectionLevel {
98        use SetIntersectionLevel::*;
99        if self.intersects(other) {
100            if self == other {
101                Equals
102            } else if self.includes(other) {
103                Includes
104            } else {
105                Intersects
106            }
107        } else {
108            Disjoint
109        }
110    }
111
112    /// Joins both sides, inserting a `/` in between them.
113    ///
114    /// This should be your preferred method when concatenating path segments.
115    ///
116    /// This is notably useful for workspaces:
117    /// ```rust
118    /// # use core::convert::TryFrom;
119    /// # use zenoh_keyexpr::OwnedKeyExpr;
120    /// # let get_workspace = || OwnedKeyExpr::try_from("some/workspace").unwrap();
121    /// let workspace: OwnedKeyExpr = get_workspace();
122    /// let topic = workspace.join("some/topic").unwrap();
123    /// ```
124    ///
125    /// If `other` is of type `&keyexpr`, you may use `self / other` instead, as the joining becomes infallible.
126    pub fn join<S: AsRef<str> + ?Sized>(&self, other: &S) -> ZResult<OwnedKeyExpr> {
127        OwnedKeyExpr::autocanonize(format!("{}/{}", self, other.as_ref()))
128    }
129
130    /// Returns `true` if `self` contains any wildcard character (`**` or `$*`).
131    #[cfg(feature = "internal")]
132    #[doc(hidden)]
133    pub fn is_wild(&self) -> bool {
134        self.is_wild_impl()
135    }
136    pub(crate) fn is_wild_impl(&self) -> bool {
137        self.0.contains(super::SINGLE_WILD as char)
138    }
139
140    pub(crate) const fn is_double_wild(&self) -> bool {
141        let bytes = self.0.as_bytes();
142        bytes.len() == 2 && bytes[0] == b'*'
143    }
144
145    /// Returns the longest prefix of `self` that doesn't contain any wildcard character (`**` or `$*`).
146    ///
147    /// NOTE: this operation can typically be used in a backend implementation, at creation of a Storage to get the keys prefix,
148    /// and then in `zenoh_backend_traits::Storage::on_sample()` this prefix has to be stripped from all received
149    /// `Sample::key_expr` to retrieve the corresponding key.
150    ///
151    /// # Examples:
152    /// ```
153    /// # use zenoh_keyexpr::keyexpr;
154    /// assert_eq!(
155    ///     Some(keyexpr::new("demo/example").unwrap()),
156    ///     keyexpr::new("demo/example/**").unwrap().get_nonwild_prefix());
157    /// assert_eq!(
158    ///     Some(keyexpr::new("demo").unwrap()),
159    ///     keyexpr::new("demo/**/test/**").unwrap().get_nonwild_prefix());
160    /// assert_eq!(
161    ///     Some(keyexpr::new("demo/example/test").unwrap()),
162    ///     keyexpr::new("demo/example/test").unwrap().get_nonwild_prefix());
163    /// assert_eq!(
164    ///     Some(keyexpr::new("demo").unwrap()),
165    ///     keyexpr::new("demo/ex$*/**").unwrap().get_nonwild_prefix());
166    /// assert_eq!(
167    ///     None,
168    ///     keyexpr::new("**").unwrap().get_nonwild_prefix());
169    /// assert_eq!(
170    ///     None,
171    ///     keyexpr::new("dem$*").unwrap().get_nonwild_prefix());
172    /// ```
173    #[cfg(feature = "internal")]
174    #[doc(hidden)]
175    pub fn get_nonwild_prefix(&self) -> Option<&keyexpr> {
176        match self.0.find('*') {
177            Some(i) => match self.0[..i].rfind('/') {
178                // SAFETY: upheld by the surrounding invariants and prior validation.
179                Some(j) => unsafe { Some(keyexpr::from_str_unchecked(&self.0[..j])) },
180                None => None, // wildcard in the first segment => no invariant prefix
181            },
182            None => Some(self), // no wildcard => return self
183        }
184    }
185
186    /// Remove the specified `prefix` from `self`.
187    /// The result is a list of `keyexpr`, since there might be several ways for the prefix to match the beginning of the `self` key expression.
188    /// For instance, if `self` is `"a/**/c/*" and `prefix` is `a/b/c` then:
189    ///   - the `prefix` matches `"a/**/c"` leading to a result of `"*"` when stripped from `self`
190    ///   - the `prefix` matches `"a/**"` leading to a result of `"**/c/*"` when stripped from `self`
191    ///
192    /// So the result is `["*", "**/c/*"]`.
193    /// If `prefix` cannot match the beginning of `self`, an empty list is reuturned.
194    ///
195    /// See below more examples.
196    ///
197    /// NOTE: this operation can typically used in a backend implementation, within the `zenoh_backend_traits::Storage::on_query()` implementation,
198    /// to transform the received `Query::selector()`'s `key_expr` into a list of key selectors
199    /// that will match all the relevant stored keys (that correspond to keys stripped from the prefix).
200    ///
201    /// # Examples:
202    /// ```
203    /// # use core::convert::{TryFrom, TryInto};
204    /// # use zenoh_keyexpr::keyexpr;
205    /// assert_eq!(
206    ///     ["abc"],
207    ///     keyexpr::new("demo/example/test/abc").unwrap().strip_prefix(keyexpr::new("demo/example/test").unwrap()).as_slice()
208    /// );
209    /// assert_eq!(
210    ///     ["**"],
211    ///     keyexpr::new("demo/example/test/**").unwrap().strip_prefix(keyexpr::new("demo/example/test").unwrap()).as_slice()
212    /// );
213    /// assert_eq!(
214    ///     ["**"],
215    ///     keyexpr::new("demo/example/**").unwrap().strip_prefix(keyexpr::new("demo/example/test").unwrap()).as_slice()
216    /// );
217    /// assert_eq!(
218    ///     ["**"],
219    ///     keyexpr::new("**").unwrap().strip_prefix(keyexpr::new("demo/example/test").unwrap()).as_slice()
220    /// );
221    /// assert_eq!(
222    ///     ["**/xyz"],
223    ///     keyexpr::new("demo/**/xyz").unwrap().strip_prefix(keyexpr::new("demo/example/test").unwrap()).as_slice()
224    /// );
225    /// assert_eq!(
226    ///     ["**"],
227    ///     keyexpr::new("demo/**/test/**").unwrap().strip_prefix(keyexpr::new("demo/example/test").unwrap()).as_slice()
228    /// );
229    /// assert_eq!(
230    ///     ["xyz", "**/ex$*/*/xyz"],
231    ///     keyexpr::new("demo/**/ex$*/*/xyz").unwrap().strip_prefix(keyexpr::new("demo/example/test").unwrap()).as_slice()
232    /// );
233    /// assert_eq!(
234    ///     ["*", "**/test/*"],
235    ///     keyexpr::new("demo/**/test/*").unwrap().strip_prefix(keyexpr::new("demo/example/test").unwrap()).as_slice()
236    /// );
237    /// assert!(
238    ///     keyexpr::new("demo/example/test/**").unwrap().strip_prefix(keyexpr::new("not/a/prefix").unwrap()).is_empty()
239    /// );
240    /// ```
241    #[cfg(feature = "internal")]
242    #[doc(hidden)]
243    pub fn strip_prefix(&self, prefix: &Self) -> Vec<&keyexpr> {
244        let mut result = alloc::vec![];
245        'chunks: for i in (0..=self.len()).rev() {
246            if if i == self.len() {
247                self.ends_with("**")
248            } else {
249                self.as_bytes()[i] == b'/'
250            } {
251                let sub_part = keyexpr::new(&self[..i]).unwrap();
252                if sub_part.intersects(prefix) {
253                    // if sub_part ends with "**", keep those in remaining part
254                    let remaining = if sub_part.ends_with("**") {
255                        &self[i - 2..]
256                    } else {
257                        &self[i + 1..]
258                    };
259                    let remaining: &keyexpr = if remaining.is_empty() {
260                        continue 'chunks;
261                    } else {
262                        remaining
263                    }
264                    .try_into()
265                    .unwrap();
266                    // if remaining is "**" return only this since it covers all
267                    if remaining.as_bytes() == b"**" {
268                        result.clear();
269                        // SAFETY: upheld by the surrounding invariants and prior validation.
270                        result.push(unsafe { keyexpr::from_str_unchecked(remaining) });
271                        return result;
272                    }
273                    for i in (0..(result.len())).rev() {
274                        if result[i].includes(remaining) {
275                            continue 'chunks;
276                        }
277                        if remaining.includes(result[i]) {
278                            result.swap_remove(i);
279                        }
280                    }
281                    result.push(remaining);
282                }
283            }
284        }
285        result
286    }
287
288    /// Remove the specified namespace `prefix` from `self`.
289    ///
290    /// This method works essentially like [`keyexpr::strip_prefix()`], but returns only the longest possible suffix.
291    /// Prefix can not contain '*' character.
292    #[cfg(feature = "internal")]
293    #[doc(hidden)]
294    pub fn strip_nonwild_prefix(&self, prefix: &nonwild_keyexpr) -> Option<&keyexpr> {
295        fn is_chunk_matching(target: &[u8], prefix: &[u8]) -> bool {
296            let mut target_idx: usize = 0;
297            let mut prefix_idx: usize = 0;
298            let mut target_prev: u8 = b'/';
299            if prefix.first() == Some(&b'@') && target.first() != Some(&b'@') {
300                // verbatim chunk can only be matched by verbatim chunk
301                return false;
302            }
303
304            while target_idx < target.len() && prefix_idx < prefix.len() {
305                if target[target_idx] == b'*' {
306                    if target_prev == b'*' || target_idx + 1 == target.len() {
307                        // either a ** wild chunk or a single * chunk at the end of the string - this matches anything
308                        return true;
309                    } else if target_prev == b'$' {
310                        for i in prefix_idx..prefix.len() - 1 {
311                            if is_chunk_matching(&target[target_idx + 1..], &prefix[i..]) {
312                                return true;
313                            }
314                        }
315                    }
316                } else if target[target_idx] == prefix[prefix_idx] {
317                    prefix_idx += 1;
318                } else if target[target_idx] != b'$' {
319                    // non-special character, which do not match the one in prefix
320                    return false;
321                }
322                target_prev = target[target_idx];
323                target_idx += 1;
324            }
325            if prefix_idx != prefix.len() {
326                // prefix was not matched entirely
327                return false;
328            }
329            target_idx == target.len()
330                || (target_idx + 2 == target.len() && target[target_idx] == b'$')
331        }
332
333        fn strip_nonwild_prefix_inner<'a>(
334            target_bytes: &'a [u8],
335            prefix_bytes: &[u8],
336        ) -> Option<&'a keyexpr> {
337            let mut target_idx = 0;
338            let mut prefix_idx = 0;
339
340            while target_idx < target_bytes.len() && prefix_idx < prefix_bytes.len() {
341                let target_end = target_idx
342                    + target_bytes[target_idx..]
343                        .iter()
344                        .position(|&i| i == b'/')
345                        .unwrap_or(target_bytes.len() - target_idx);
346                let prefix_end = prefix_idx
347                    + prefix_bytes[prefix_idx..]
348                        .iter()
349                        .position(|&i| i == b'/')
350                        .unwrap_or(prefix_bytes.len() - prefix_idx);
351                let target_chunk = &target_bytes[target_idx..target_end];
352                if target_chunk.len() == 2 && target_chunk[0] == b'*' {
353                    let remaining_prefix = &prefix_bytes[prefix_idx..];
354                    return match remaining_prefix.iter().position(|&x| x == b'@') {
355                        Some(mut p) => {
356                            if target_end + 1 >= target_bytes.len() {
357                                // "**" is the last chunk, and it is not allowed to match @verbatim chunks, so we stop here
358                                return None;
359                            } else {
360                                loop {
361                                    // try to use "**" to match as many non-verbatim chunks as possible
362                                    if let Some(ke) = strip_nonwild_prefix_inner(
363                                        &target_bytes[(target_end + 1)..],
364                                        &remaining_prefix[p..],
365                                    ) {
366                                        return Some(ke);
367                                    } else if p == 0 {
368                                        // "**" is not allowed to match @verbatim chunks
369                                        return None;
370                                    } else {
371                                        // search for the beginning of the next chunk from the end
372                                        p -= 2;
373                                        while p > 0 && remaining_prefix[p - 1] != b'/' {
374                                            p -= 1;
375                                        }
376                                    }
377                                }
378                            }
379                        }
380                        // SAFETY: upheld by the surrounding invariants and prior validation.
381                        None => unsafe {
382                            // "**" can match all remaining non-verbatim chunks
383                            Some(keyexpr::from_str_unchecked(core::str::from_utf8_unchecked(
384                                &target_bytes[target_idx..],
385                            )))
386                        },
387                    };
388                }
389                if target_end == target_bytes.len() {
390                    // target contains no more chunks than prefix and the last one is non double-wild - so it can not match
391                    return None;
392                }
393                let prefix_chunk = &prefix_bytes[prefix_idx..prefix_end];
394                if !is_chunk_matching(target_chunk, prefix_chunk) {
395                    return None;
396                }
397                if prefix_end == prefix_bytes.len() {
398                    // SAFETY: every chunk of keyexpr is also a valid keyexpr
399                    return unsafe {
400                        Some(keyexpr::from_str_unchecked(core::str::from_utf8_unchecked(
401                            &target_bytes[(target_end + 1)..],
402                        )))
403                    };
404                }
405                target_idx = target_end + 1;
406                prefix_idx = prefix_end + 1;
407            }
408            None
409        }
410
411        let target_bytes = self.0.as_bytes();
412        let prefix_bytes = prefix.0.as_bytes();
413
414        strip_nonwild_prefix_inner(target_bytes, prefix_bytes)
415    }
416
417    pub const fn as_str(&self) -> &str {
418        &self.0
419    }
420
421    /// # Safety
422    /// This constructs a [`keyexpr`] without ensuring that it is a valid key-expression.
423    ///
424    /// Much like [`core::str::from_utf8_unchecked`], this is memory-safe, but calling this without maintaining
425    /// [`keyexpr`]'s invariants yourself may lead to unexpected behaviors, the Zenoh network dropping your messages.
426    pub const unsafe fn from_str_unchecked(s: &str) -> &Self {
427        // SAFETY: `keyexpr` is `repr(transparent)` over `str`.
428        unsafe { core::mem::transmute(s) }
429    }
430
431    /// # Safety
432    /// This constructs a [`keyexpr`] without ensuring that it is a valid key-expression.
433    ///
434    /// Much like [`core::str::from_utf8_unchecked`], this is memory-safe, but calling this without maintaining
435    /// [`keyexpr`]'s invariants yourself may lead to unexpected behaviors, the Zenoh network dropping your messages.
436    pub unsafe fn from_slice_unchecked(s: &[u8]) -> &Self {
437        // SAFETY: `keyexpr` is `repr(transparent)` over `str` and caller guarantees valid UTF-8 keyexpr bytes.
438        unsafe { core::mem::transmute(s) }
439    }
440
441    #[cfg(feature = "internal")]
442    #[doc(hidden)]
443    pub const fn chunks(&self) -> Chunks<'_> {
444        self.chunks_impl()
445    }
446    pub(crate) const fn chunks_impl(&self) -> Chunks<'_> {
447        Chunks {
448            inner: self.as_str(),
449        }
450    }
451    pub(crate) fn next_delimiter(&self, i: usize) -> Option<usize> {
452        self.as_str()
453            .get(i + 1..)
454            .and_then(|s| s.find('/').map(|j| i + 1 + j))
455    }
456    pub(crate) fn previous_delimiter(&self, i: usize) -> Option<usize> {
457        self.as_str().get(..i).and_then(|s| s.rfind('/'))
458    }
459    pub(crate) fn first_byte(&self) -> u8 {
460        // SAFETY: upheld by the surrounding invariants and prior validation.
461        unsafe { *self.as_bytes().get_unchecked(0) }
462    }
463    pub(crate) fn iter_splits_ltr(&self) -> SplitsLeftToRight<'_> {
464        SplitsLeftToRight {
465            inner: self,
466            index: 0,
467        }
468    }
469    pub(crate) fn iter_splits_rtl(&self) -> SplitsRightToLeft<'_> {
470        SplitsRightToLeft {
471            inner: self,
472            index: self.len(),
473        }
474    }
475}
476#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
477pub(crate) struct SplitsLeftToRight<'a> {
478    inner: &'a keyexpr,
479    index: usize,
480}
481impl<'a> SplitsLeftToRight<'a> {
482    fn right(&self) -> &'a str {
483        &self.inner[self.index + ((self.index != 0) as usize)..]
484    }
485    fn left(&self, followed_by_double: bool) -> &'a str {
486        &self.inner[..(self.index + ((self.index != 0) as usize + 2) * followed_by_double as usize)]
487    }
488}
489impl<'a> Iterator for SplitsLeftToRight<'a> {
490    type Item = (&'a keyexpr, &'a keyexpr);
491    fn next(&mut self) -> Option<Self::Item> {
492        match self.index < self.inner.len() {
493            false => None,
494            true => {
495                let right = self.right();
496                let double_wild = right.starts_with("**");
497                let left = self.left(double_wild);
498                self.index = if left.is_empty() {
499                    self.inner.next_delimiter(0).unwrap_or(self.inner.len())
500                } else {
501                    self.inner
502                        .next_delimiter(left.len())
503                        .unwrap_or(self.inner.len() + (left.len() == self.inner.len()) as usize)
504                };
505                if left.is_empty() {
506                    self.next()
507                } else {
508                    // SAFETY: because any keyexpr split at `/` becomes 2 valid keyexprs by design, it's safe to assume the constraint is valid once both sides have been validated to not be empty.
509                    (!right.is_empty()).then(|| unsafe {
510                        (
511                            keyexpr::from_str_unchecked(left),
512                            keyexpr::from_str_unchecked(right),
513                        )
514                    })
515                }
516            }
517        }
518    }
519}
520#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
521pub(crate) struct SplitsRightToLeft<'a> {
522    inner: &'a keyexpr,
523    index: usize,
524}
525impl<'a> SplitsRightToLeft<'a> {
526    fn right(&self, followed_by_double: bool) -> &'a str {
527        &self.inner[(self.index
528            - ((self.index != self.inner.len()) as usize + 2) * followed_by_double as usize)..]
529    }
530    fn left(&self) -> &'a str {
531        &self.inner[..(self.index - ((self.index != self.inner.len()) as usize))]
532    }
533}
534impl<'a> Iterator for SplitsRightToLeft<'a> {
535    type Item = (&'a keyexpr, &'a keyexpr);
536    fn next(&mut self) -> Option<Self::Item> {
537        match self.index {
538            0 => None,
539            _ => {
540                let left = self.left();
541                let double_wild = left.ends_with("**");
542                let right = self.right(double_wild);
543                self.index = if right.is_empty() {
544                    self.inner
545                        .previous_delimiter(self.inner.len())
546                        .map_or(0, |n| n + 1)
547                } else {
548                    self.inner
549                        .previous_delimiter(
550                            self.inner.len()
551                                - right.len()
552                                - (self.inner.len() != right.len()) as usize,
553                        )
554                        .map_or(0, |n| n + 1)
555                };
556                if right.is_empty() {
557                    self.next()
558                } else {
559                    // SAFETY: because any keyexpr split at `/` becomes 2 valid keyexprs by design, it's safe to assume the constraint is valid once both sides have been validated to not be empty.
560                    (!left.is_empty()).then(|| unsafe {
561                        (
562                            keyexpr::from_str_unchecked(left),
563                            keyexpr::from_str_unchecked(right),
564                        )
565                    })
566                }
567            }
568        }
569    }
570}
571#[test]
572fn splits() {
573    let ke = keyexpr::new("a/**/b/c").unwrap();
574    let mut splits = ke.iter_splits_ltr();
575    assert_eq!(
576        splits.next(),
577        Some((
578            keyexpr::new("a/**").unwrap(),
579            keyexpr::new("**/b/c").unwrap()
580        ))
581    );
582    assert_eq!(
583        splits.next(),
584        Some((keyexpr::new("a/**/b").unwrap(), keyexpr::new("c").unwrap()))
585    );
586    assert_eq!(splits.next(), None);
587    let mut splits = ke.iter_splits_rtl();
588    assert_eq!(
589        splits.next(),
590        Some((keyexpr::new("a/**/b").unwrap(), keyexpr::new("c").unwrap()))
591    );
592    assert_eq!(
593        splits.next(),
594        Some((
595            keyexpr::new("a/**").unwrap(),
596            keyexpr::new("**/b/c").unwrap()
597        ))
598    );
599    assert_eq!(splits.next(), None);
600    let ke = keyexpr::new("**").unwrap();
601    let mut splits = ke.iter_splits_ltr();
602    assert_eq!(
603        splits.next(),
604        Some((keyexpr::new("**").unwrap(), keyexpr::new("**").unwrap()))
605    );
606    assert_eq!(splits.next(), None);
607    let ke = keyexpr::new("ab").unwrap();
608    let mut splits = ke.iter_splits_ltr();
609    assert_eq!(splits.next(), None);
610    let ke = keyexpr::new("ab/cd").unwrap();
611    let mut splits = ke.iter_splits_ltr();
612    assert_eq!(
613        splits.next(),
614        Some((keyexpr::new("ab").unwrap(), keyexpr::new("cd").unwrap()))
615    );
616    assert_eq!(splits.next(), None);
617    for (i, ke) in crate::fuzzer::KeyExprFuzzer(rand::thread_rng())
618        .take(100)
619        .enumerate()
620    {
621        dbg!(i, &ke);
622        let splits = ke.iter_splits_ltr().collect::<Vec<_>>();
623        assert_eq!(splits, {
624            let mut rtl_rev = ke.iter_splits_rtl().collect::<Vec<_>>();
625            rtl_rev.reverse();
626            rtl_rev
627        });
628        assert!(!splits
629            .iter()
630            .any(|s| s.0.ends_with('/') || s.1.starts_with('/')));
631    }
632}
633
634#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
635pub struct Chunks<'a> {
636    inner: &'a str,
637}
638impl<'a> Chunks<'a> {
639    /// Convert the remaining part of the iterator to a keyexpr if it is not empty.
640    pub const fn as_keyexpr(self) -> Option<&'a keyexpr> {
641        match self.inner.is_empty() {
642            true => None,
643            // SAFETY: upheld by the surrounding invariants and prior validation.
644            _ => Some(unsafe { keyexpr::from_str_unchecked(self.inner) }),
645        }
646    }
647    /// Peek at the next chunk without consuming it.
648    pub fn peek(&self) -> Option<&keyexpr> {
649        if self.inner.is_empty() {
650            None
651        } else {
652            // SAFETY: upheld by the surrounding invariants and prior validation.
653            Some(unsafe {
654                keyexpr::from_str_unchecked(
655                    &self.inner[..self.inner.find('/').unwrap_or(self.inner.len())],
656                )
657            })
658        }
659    }
660    /// Peek at the last chunk without consuming it.
661    pub fn peek_back(&self) -> Option<&keyexpr> {
662        if self.inner.is_empty() {
663            None
664        } else {
665            // SAFETY: upheld by the surrounding invariants and prior validation.
666            Some(unsafe {
667                keyexpr::from_str_unchecked(
668                    &self.inner[self.inner.rfind('/').map_or(0, |i| i + 1)..],
669                )
670            })
671        }
672    }
673}
674impl<'a> Iterator for Chunks<'a> {
675    type Item = &'a keyexpr;
676    fn next(&mut self) -> Option<Self::Item> {
677        if self.inner.is_empty() {
678            return None;
679        }
680        let (next, inner) = self.inner.split_once('/').unwrap_or((self.inner, ""));
681        self.inner = inner;
682        // SAFETY: upheld by the surrounding invariants and prior validation.
683        Some(unsafe { keyexpr::from_str_unchecked(next) })
684    }
685}
686impl DoubleEndedIterator for Chunks<'_> {
687    fn next_back(&mut self) -> Option<Self::Item> {
688        if self.inner.is_empty() {
689            return None;
690        }
691        let (inner, next) = self.inner.rsplit_once('/').unwrap_or(("", self.inner));
692        self.inner = inner;
693        // SAFETY: upheld by the surrounding invariants and prior validation.
694        Some(unsafe { keyexpr::from_str_unchecked(next) })
695    }
696}
697
698impl Div for &keyexpr {
699    type Output = OwnedKeyExpr;
700    fn div(self, rhs: Self) -> Self::Output {
701        self.join(rhs).unwrap() // Joining 2 key expressions should always result in a canonizable string.
702    }
703}
704
705/// The possible relations between two sets of key expressions defined by glob patterns.
706///
707/// Each glob key expression defines a set of possible concrete key expressions that it matches.
708/// This enum describes how two such sets relate to each other:
709///
710/// - [`Disjoint`](SetIntersectionLevel::Disjoint): The sets have no key expressions in common.
711///   Example: `foo/*` and `bar/*` - no overlap.
712/// - [`Intersects`](SetIntersectionLevel::Intersects): The sets have some key expressions in common, but neither fully contains the other.
713///   Example: `foo/*` and `*/bar` - `foo/bar` matches both.
714/// - [`Includes`](SetIntersectionLevel::Includes): The first set fully contains the second set.
715///   Example: `foo/**` includes `foo/*` (where `**` matches any number of sections).
716/// - [`Equals`](SetIntersectionLevel::Equals): The sets are identical.
717///   Example: `foo/*` and `foo/*`.
718///
719/// Note that [`Equals`](SetIntersectionLevel::Equals) implies [`Includes`](SetIntersectionLevel::Includes), which itself implies [`Intersects`](SetIntersectionLevel::Intersects).
720///
721/// You can check for intersection with `level >= SetIntersectionLevel::Intersects` and for inclusion with `level >= SetIntersectionLevel::Includes`.
722#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
723#[cfg(feature = "unstable")]
724pub enum SetIntersectionLevel {
725    Disjoint,
726    Intersects,
727    Includes,
728    Equals,
729}
730
731#[cfg(feature = "unstable")]
732#[test]
733fn intersection_level_cmp() {
734    use SetIntersectionLevel::*;
735    assert!(Disjoint < Intersects);
736    assert!(Intersects < Includes);
737    assert!(Includes < Equals);
738}
739
740impl fmt::Debug for keyexpr {
741    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
742        write!(f, "ke`{}`", self.as_ref())
743    }
744}
745
746impl fmt::Display for keyexpr {
747    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
748        f.write_str(self)
749    }
750}
751
752#[repr(i8)]
753enum KeyExprError {
754    LoneDollarStar = -1,
755    SingleStarAfterDoubleStar = -2,
756    DoubleStarAfterDoubleStar = -3,
757    EmptyChunk = -4,
758    StarInChunk = -5,
759    DollarAfterDollar = -6,
760    SharpOrQMark = -7,
761    UnboundDollar = -8,
762}
763
764impl KeyExprError {
765    #[cold]
766    fn into_err(self, s: &str) -> ZError {
767        let error = match &self {
768            Self::LoneDollarStar => anyhow!("Invalid Key Expr `{s}`: empty chunks are forbidden, as well as leading and trailing slashes"),
769            Self::SingleStarAfterDoubleStar => anyhow!("Invalid Key Expr `{s}`: `**/*` must be replaced by `*/**` to reach canon-form"),
770            Self::DoubleStarAfterDoubleStar => anyhow!("Invalid Key Expr `{s}`: `**/**` must be replaced by `**` to reach canon-form"),
771            Self::EmptyChunk => anyhow!("Invalid Key Expr `{s}`: empty chunks are forbidden, as well as leading and trailing slashes"),
772            Self::StarInChunk => anyhow!("Invalid Key Expr `{s}`: `*` may only be preceded by `/` or `$`"),
773            Self::DollarAfterDollar => anyhow!("Invalid Key Expr `{s}`: `$` is not allowed after `$*`"),
774            Self::SharpOrQMark => anyhow!("Invalid Key Expr `{s}`: `#` and `?` are forbidden characters"),
775            Self::UnboundDollar => anyhow!("Invalid Key Expr `{s}`: `$` is only allowed in `$*`")
776        };
777        zerror!((self) error).into()
778    }
779}
780
781impl<'a> TryFrom<&'a str> for &'a keyexpr {
782    type Error = ZError;
783
784    fn try_from(value: &'a str) -> Result<Self, Self::Error> {
785        use KeyExprError::*;
786        // Check for emptiness or trailing slash, as they are not caught after.
787        if value.is_empty() || value.ends_with('/') {
788            return Err(EmptyChunk.into_err(value));
789        }
790        let bytes = value.as_bytes();
791        // The start of the chunk, i.e. the index of the char after the '/'.
792        let mut chunk_start = 0;
793        // Use a while loop to scan the string because it requires to advance the iteration
794        // manually for some characters, e.g. '$'.
795        let mut i = 0;
796        while i < bytes.len() {
797            match bytes[i] {
798                // In UTF-8, all keyexpr special characters are lesser or equal to '/', except '?'
799                // This shortcut greatly reduce the number of operations for alphanumeric
800                // characters, which are the most common in keyexprs.
801                c if c > b'/' && c != b'?' => i += 1,
802                // A chunk cannot start with '/'
803                b'/' if i == chunk_start => return Err(EmptyChunk.into_err(value)),
804                // `chunk_start` is updated when starting a new chunk.
805                b'/' => {
806                    i += 1;
807                    chunk_start = i;
808                }
809                // The first encountered '*' must be at the beginning of a chunk
810                b'*' if i != chunk_start => return Err(StarInChunk.into_err(value)),
811                // When a '*' is match, it means this is a wildcard chunk, possibly with two "**",
812                // which must be followed by a '/' or the end of the string.
813                // So the next character is checked, and the cursor
814                b'*' => match bytes.get(i + 1) {
815                    // Break if end of string is reached.
816                    None => break,
817                    // If a '/' is found, start a new chunk, and advance the cursor to take in
818                    // previous check.
819                    Some(&b'/') => {
820                        i += 2;
821                        chunk_start = i;
822                    }
823                    // If a second '*' is found, the next character must be a slash.
824                    Some(&b'*') => match bytes.get(i + 2) {
825                        // Break if end of string is reached.
826                        None => break,
827                        // Because a "**" chunk cannot be followed by "*" or "**", the next char is
828                        // checked to not be a '*'.
829                        Some(&b'/') if matches!(bytes.get(i + 3), Some(b'*')) => {
830                            // If there are two consecutive wildcard chunks, raise the appropriate
831                            // error.
832                            #[cold]
833                            fn double_star_err(value: &str, i: usize) -> ZError {
834                                match (value.as_bytes().get(i + 4), value.as_bytes().get(i + 5)) {
835                                    (None | Some(&b'/'), _) => SingleStarAfterDoubleStar,
836                                    (Some(&b'*'), None | Some(&b'/')) => DoubleStarAfterDoubleStar,
837                                    _ => StarInChunk,
838                                }
839                                .into_err(value)
840                            }
841                            return Err(double_star_err(value, i));
842                        }
843                        // If a '/' is found, start a new chunk, and advance the cursor to take in
844                        // previous checks.
845                        Some(&b'/') => {
846                            i += 3;
847                            chunk_start = i;
848                        }
849                        // This is not a "**" chunk, raise an error.
850                        _ => return Err(StarInChunk.into_err(value)),
851                    },
852                    // This is not a "*" chunk, raise an error.
853                    _ => return Err(StarInChunk.into_err(value)),
854                },
855                // A '$' must be followed by '*'.
856                b'$' if bytes.get(i + 1) != Some(&b'*') => {
857                    return Err(UnboundDollar.into_err(value))
858                }
859                // "$*" has some additional rules to check.
860                b'$' => match bytes.get(i + 2) {
861                    // "$*" cannot be followed by '$'.
862                    Some(&b'$') => return Err(DollarAfterDollar.into_err(value)),
863                    // "$*" cannot be alone in a chunk
864                    Some(&b'/') | None if i == chunk_start => {
865                        return Err(LoneDollarStar.into_err(value))
866                    }
867                    // Break if end of string is reached.
868                    None => break,
869                    // Everything is fine, advance the cursor taking the '*' check in account.
870                    _ => i += 2,
871                },
872                // '#' and '?' are forbidden.
873                b'#' | b'?' => return Err(SharpOrQMark.into_err(value)),
874                // Fallback for unmatched characters
875                _ => i += 1,
876            }
877        }
878        // SAFETY: upheld by the surrounding invariants and prior validation.
879        Ok(unsafe { keyexpr::from_str_unchecked(value) })
880    }
881}
882
883impl<'a> TryFrom<&'a mut str> for &'a keyexpr {
884    type Error = ZError;
885    fn try_from(value: &'a mut str) -> Result<Self, Self::Error> {
886        (value as &'a str).try_into()
887    }
888}
889
890impl<'a> TryFrom<&'a mut String> for &'a keyexpr {
891    type Error = ZError;
892    fn try_from(value: &'a mut String) -> Result<Self, Self::Error> {
893        (value.as_str()).try_into()
894    }
895}
896
897impl<'a> TryFrom<&'a String> for &'a keyexpr {
898    type Error = ZError;
899    fn try_from(value: &'a String) -> Result<Self, Self::Error> {
900        (value.as_str()).try_into()
901    }
902}
903impl<'a> TryFrom<&'a &'a str> for &'a keyexpr {
904    type Error = ZError;
905    fn try_from(value: &'a &'a str) -> Result<Self, Self::Error> {
906        (*value).try_into()
907    }
908}
909impl<'a> TryFrom<&'a &'a mut str> for &'a keyexpr {
910    type Error = ZError;
911    fn try_from(value: &'a &'a mut str) -> Result<Self, Self::Error> {
912        keyexpr::new(*value)
913    }
914}
915#[test]
916fn autocanon() {
917    let mut s: Box<str> = Box::from("hello/**/*");
918    let mut s: &mut str = &mut s;
919    assert_eq!(keyexpr::autocanonize(&mut s).unwrap(), "hello/*/**");
920}
921
922impl Deref for keyexpr {
923    type Target = str;
924    fn deref(&self) -> &Self::Target {
925        // SAFETY: upheld by the surrounding invariants and prior validation.
926        unsafe { core::mem::transmute(self) }
927    }
928}
929impl AsRef<str> for keyexpr {
930    fn as_ref(&self) -> &str {
931        self
932    }
933}
934
935impl PartialEq<str> for keyexpr {
936    fn eq(&self, other: &str) -> bool {
937        self.as_str() == other
938    }
939}
940
941impl PartialEq<keyexpr> for str {
942    fn eq(&self, other: &keyexpr) -> bool {
943        self == other.as_str()
944    }
945}
946
947impl Borrow<keyexpr> for OwnedKeyExpr {
948    fn borrow(&self) -> &keyexpr {
949        self
950    }
951}
952impl ToOwned for keyexpr {
953    type Owned = OwnedKeyExpr;
954    fn to_owned(&self) -> Self::Owned {
955        OwnedKeyExpr::from(self)
956    }
957}
958
959/// A keyexpr that is statically known not to contain any wild chunks.
960#[allow(non_camel_case_types)]
961#[repr(transparent)]
962#[derive(PartialEq, Eq, Hash, PartialOrd, Ord, Debug)]
963pub struct nonwild_keyexpr(keyexpr);
964
965impl nonwild_keyexpr {
966    /// Attempts to construct a non-wild key expression from anything convertible to keyexpression.
967    ///
968    /// Will return an Err if `t` isn't a valid key expression.
969    pub fn new<'a, T, E>(t: &'a T) -> Result<&'a Self, ZError>
970    where
971        &'a keyexpr: TryFrom<&'a T, Error = E>,
972        E: Into<ZError>,
973        T: ?Sized,
974    {
975        let ke: &'a keyexpr = t.try_into().map_err(|e: E| e.into())?;
976        ke.try_into()
977    }
978
979    /// # Safety
980    /// This constructs a [`nonwild_keyexpr`] without ensuring that it is a valid key-expression without wild chunks.
981    ///
982    /// Much like [`core::str::from_utf8_unchecked`], this is memory-safe, but calling this without maintaining
983    /// [`nonwild_keyexpr`]'s invariants yourself may lead to unexpected behaviors, the Zenoh network dropping your messages.
984    pub const unsafe fn from_str_unchecked(s: &str) -> &Self {
985        // SAFETY: `nonwild_keyexpr` is `repr(transparent)` over `keyexpr`/`str`.
986        unsafe { core::mem::transmute(s) }
987    }
988}
989
990impl Deref for nonwild_keyexpr {
991    type Target = keyexpr;
992    fn deref(&self) -> &Self::Target {
993        &self.0
994    }
995}
996
997impl<'a> TryFrom<&'a keyexpr> for &'a nonwild_keyexpr {
998    type Error = ZError;
999    fn try_from(value: &'a keyexpr) -> Result<Self, Self::Error> {
1000        if value.is_wild_impl() {
1001            bail!("nonwild_keyexpr can not contain any wild chunks")
1002        }
1003        // SAFETY: upheld by the surrounding invariants and prior validation.
1004        Ok(unsafe { core::mem::transmute::<&keyexpr, &nonwild_keyexpr>(value) })
1005    }
1006}
1007
1008impl Borrow<nonwild_keyexpr> for OwnedNonWildKeyExpr {
1009    fn borrow(&self) -> &nonwild_keyexpr {
1010        self
1011    }
1012}
1013
1014impl ToOwned for nonwild_keyexpr {
1015    type Owned = OwnedNonWildKeyExpr;
1016    fn to_owned(&self) -> Self::Owned {
1017        OwnedNonWildKeyExpr::from(self)
1018    }
1019}
1020
1021#[cfg(feature = "internal")]
1022#[test]
1023fn test_keyexpr_strip_prefix() {
1024    let expectations = [
1025        (("demo/example/test/**", "demo/example/test"), &["**"][..]),
1026        (("demo/example/**", "demo/example/test"), &["**"]),
1027        (("**", "demo/example/test"), &["**"]),
1028        (
1029            ("demo/example/test/**/x$*/**", "demo/example/test"),
1030            &["**/x$*/**"],
1031        ),
1032        (("demo/**/xyz", "demo/example/test"), &["**/xyz"]),
1033        (("demo/**/test/**", "demo/example/test"), &["**"]),
1034        (
1035            ("demo/**/ex$*/*/xyz", "demo/example/test"),
1036            ["xyz", "**/ex$*/*/xyz"].as_ref(),
1037        ),
1038        (
1039            ("demo/**/ex$*/t$*/xyz", "demo/example/test"),
1040            ["xyz", "**/ex$*/t$*/xyz"].as_ref(),
1041        ),
1042        (
1043            ("demo/**/te$*/*/xyz", "demo/example/test"),
1044            ["*/xyz", "**/te$*/*/xyz"].as_ref(),
1045        ),
1046        (("demo/example/test", "demo/example/test"), [].as_ref()),
1047    ]
1048    .map(|((a, b), expected)| {
1049        (
1050            (keyexpr::new(a).unwrap(), keyexpr::new(b).unwrap()),
1051            expected
1052                .iter()
1053                .map(|s| keyexpr::new(*s).unwrap())
1054                .collect::<Vec<_>>(),
1055        )
1056    });
1057    for ((ke, prefix), expected) in expectations {
1058        dbg!(ke, prefix);
1059        assert_eq!(ke.strip_prefix(prefix), expected)
1060    }
1061}
1062
1063#[cfg(feature = "internal")]
1064#[test]
1065fn test_keyexpr_strip_nonwild_prefix() {
1066    let expectations = [
1067        (("demo/example/test/**", "demo/example/test"), Some("**")),
1068        (("demo/example/**", "demo/example/test"), Some("**")),
1069        (("**", "demo/example/test"), Some("**")),
1070        (("*/example/test/1", "demo/example/test"), Some("1")),
1071        (("demo/*/test/1", "demo/example/test"), Some("1")),
1072        (("*/*/test/1", "demo/example/test"), Some("1")),
1073        (("*/*/*/1", "demo/example/test"), Some("1")),
1074        (("*/test/1", "demo/example/test"), None),
1075        (("*/*/1", "demo/example/test"), None),
1076        (("*/*/**", "demo/example/test"), Some("**")),
1077        (
1078            ("demo/example/test/**/x$*/**", "demo/example/test"),
1079            Some("**/x$*/**"),
1080        ),
1081        (("demo/**/xyz", "demo/example/test"), Some("**/xyz")),
1082        (("demo/**/test/**", "demo/example/test"), Some("**/test/**")),
1083        (
1084            ("demo/**/ex$*/*/xyz", "demo/example/test"),
1085            Some("**/ex$*/*/xyz"),
1086        ),
1087        (
1088            ("demo/**/ex$*/t$*/xyz", "demo/example/test"),
1089            Some("**/ex$*/t$*/xyz"),
1090        ),
1091        (
1092            ("demo/**/te$*/*/xyz", "demo/example/test"),
1093            Some("**/te$*/*/xyz"),
1094        ),
1095        (("demo/example/test", "demo/example/test"), None),
1096        (("demo/example/test1/something", "demo/example/test"), None),
1097        (
1098            ("demo/example/test$*/something", "demo/example/test"),
1099            Some("something"),
1100        ),
1101        (("*/example/test/something", "@demo/example/test"), None),
1102        (("**/test/something", "@demo/example/test"), None),
1103        (("**/test/something", "demo/example/@test"), None),
1104        (
1105            ("@demo/*/test/something", "@demo/example/test"),
1106            Some("something"),
1107        ),
1108        (("@demo/*/test/something", "@demo/@example/test"), None),
1109        (("**/@demo/test/something", "@demo/test"), Some("something")),
1110        (("**/@test/something", "demo/@test"), Some("something")),
1111        (
1112            ("@demo/**/@test/something", "@demo/example/@test"),
1113            Some("something"),
1114        ),
1115    ]
1116    .map(|((a, b), expected)| {
1117        (
1118            (keyexpr::new(a).unwrap(), nonwild_keyexpr::new(b).unwrap()),
1119            expected.map(|t| keyexpr::new(t).unwrap()),
1120        )
1121    });
1122    for ((ke, prefix), expected) in expectations {
1123        dbg!(ke, prefix);
1124        assert_eq!(ke.strip_nonwild_prefix(prefix), expected)
1125    }
1126}
1127
1128#[cfg(test)]
1129mod tests {
1130    use test_case::test_case;
1131    use zenoh_result::ErrNo;
1132
1133    use crate::{
1134        key_expr::borrowed::{KeyExprError, KeyExprError::*},
1135        keyexpr,
1136    };
1137
1138    #[test_case("", EmptyChunk; "Empty")]
1139    #[test_case("demo/example/test", None; "Normal key_expr")]
1140    #[test_case("demo/*", None; "Single star at the end")]
1141    #[test_case("demo/**", None; "Double star at the end")]
1142    #[test_case("demo/*/*/test", None; "Single star after single star")]
1143    #[test_case("demo/*/**/test", None; "Double star after single star")]
1144    #[test_case("demo/example$*/test", None; "Dollar with star")]
1145    #[test_case("demo/example$*-$*/test", None; "Multiple dollar with star")]
1146    #[test_case("/demo/example/test", EmptyChunk; "Leading /")]
1147    #[test_case("demo/example/test/", EmptyChunk; "Trailing /")]
1148    #[test_case("demo/$*/test", LoneDollarStar; "Lone $*")]
1149    #[test_case("demo/$*", LoneDollarStar; "Lone $* at the end")]
1150    #[test_case("demo/example$*", None; "Trailing lone $*")]
1151    #[test_case("demo/**/*/test", SingleStarAfterDoubleStar; "Single star after double star")]
1152    #[test_case("demo/**/**/test", DoubleStarAfterDoubleStar; "Double star after double star")]
1153    #[test_case("demo//test", EmptyChunk; "Empty Chunk")]
1154    #[test_case("demo/exam*ple/test", StarInChunk; "Star in chunk")]
1155    #[test_case("demo/example$*$/test", DollarAfterDollar; "Dollar after dollar or star")]
1156    #[test_case("demo/example$*$*/test", DollarAfterDollar; "Dollar star after dollar or star")]
1157    #[test_case("demo/example#/test", SharpOrQMark; "Contain sharp")]
1158    #[test_case("demo/example?/test", SharpOrQMark; "Contain mark")]
1159    #[test_case("demo/$/test", UnboundDollar; "Contain unbounded dollar")]
1160    fn test_keyexpr_new(key_str: &str, error: impl Into<Option<KeyExprError>>) {
1161        assert_eq!(
1162            keyexpr::new(key_str).err().map(|err| {
1163                err.downcast_ref::<zenoh_result::ZError>()
1164                    .unwrap()
1165                    .errno()
1166                    .get()
1167            }),
1168            error.into().map(|err| err as i8)
1169        );
1170    }
1171}