Skip to main content

radixmap/
rule.rs

1//! Rule represents a match
2use super::defs::*;
3use std::str::from_utf8;
4use std::str::from_utf8_unchecked;
5
6/// An enum representing various matching patterns
7#[derive(Clone)]
8pub enum RadixRule {
9    /// Plain rule that accepts arbitrary strings
10    ///
11    /// # Syntax
12    ///
13    /// - /
14    /// - /api
15    ///
16    Plain {
17        /// fragment
18        frag: Bytes
19    },
20
21    /// Named param matches a segment of the route
22    ///
23    /// # Syntax
24    ///
25    /// - :
26    /// - :id
27    ///
28    Param {
29        /// fragment
30        frag: Bytes,
31
32        /// param's name
33        name: Bytes,
34    },
35
36    /// Unix glob style matcher, note that it must be the last component of a route
37    ///
38    /// # Syntax
39    ///
40    /// - *
41    ///
42    Glob {
43        /// fragment
44        frag: Bytes,
45
46        /// glob pattern
47        glob: glob::Pattern
48    },
49
50    /// Perl-like regular expressions
51    ///
52    /// # Syntax
53    ///
54    /// - {}
55    /// - {:}
56    /// - {\d+}
57    /// - {:\d+}
58    /// - {id:\d+}
59    ///
60    Regex {
61        /// fragment
62        frag: Bytes,
63
64        /// regex's name
65        name: Bytes,
66
67        /// the regex
68        expr: Regex,
69    },
70}
71
72impl RadixRule {
73    /// Create a plain text rule
74    ///
75    /// # Examples
76    ///
77    /// ```
78    /// use radixmap::{rule::RadixRule};
79    ///
80    /// assert!(RadixRule::from_plain("").is_ok());
81    /// assert!(RadixRule::from_plain("id").is_ok());
82    /// ```
83    #[inline]
84    pub fn from_plain(frag: impl Into<Bytes>) -> RadixResult<Self> {
85        Ok(Self::Plain { frag: frag.into() })
86    }
87
88    /// Create a named param rule
89    ///
90    /// # Examples
91    ///
92    /// ```
93    /// use radixmap::{rule::RadixRule};
94    ///
95    /// assert!(RadixRule::from_param(":").is_ok());   // segment placeholder
96    /// assert!(RadixRule::from_param(":id").is_ok()); // param with a name
97    /// assert!(RadixRule::from_param("").is_err());   // missing :
98    /// assert!(RadixRule::from_param("id").is_err()); // missing :
99    /// ```
100    #[inline]
101    pub fn from_param(frag: impl Into<Bytes>) -> RadixResult<Self> {
102        let frag = frag.into();
103
104        if !frag.starts_with(b":") {
105            return Err(RadixError::PathMalformed("param lack of colon"));
106        }
107
108        let name = frag.slice(1..);
109        Ok(Self::Param { frag, name })
110    }
111
112    /// Create a unix glob style rule
113    ///
114    /// # Examples
115    ///
116    /// ```
117    /// use radixmap::{rule::RadixRule};
118    ///
119    /// assert!(RadixRule::from_glob("*").is_ok());      // match entire string
120    /// assert!(RadixRule::from_glob("*id").is_ok());    // match strings ending with 'id'
121    /// assert!(RadixRule::from_glob("").is_err());      // missing rule chars
122    /// assert!(RadixRule::from_glob("id").is_err());    // missing rule chars
123    /// ```
124    #[inline]
125    pub fn from_glob(frag: impl Into<Bytes>) -> RadixResult<Self> {
126        let frag = frag.into();
127
128        if !frag.starts_with(b"*") {
129            return Err(RadixError::PathMalformed("glob lack of asterisk"));
130        }
131
132        let glob = glob::Pattern::new(from_utf8(frag.as_ref())?)?;
133        Ok(Self::Glob { frag, glob })
134    }
135
136    /// Create a regular expression rule
137    ///
138    /// # Examples
139    ///
140    /// ```
141    /// use radixmap::{rule::RadixRule};
142    ///
143    /// assert!(RadixRule::from_regex(r"{}").is_ok());       // useless but valid
144    /// assert!(RadixRule::from_regex(r"{:}").is_ok());      // same as above
145    /// assert!(RadixRule::from_regex(r"{\d+}").is_ok());    // name is empty
146    /// assert!(RadixRule::from_regex(r"{:\d+}").is_ok());   // same as above
147    /// assert!(RadixRule::from_regex(r"{id:\d+}").is_ok()); // regex with a name
148    /// assert!(RadixRule::from_regex(r"").is_err());        // missing {}
149    /// assert!(RadixRule::from_regex(r"\d+").is_err());     // missing {}
150    /// assert!(RadixRule::from_regex(r"{").is_err());       // missing }
151    /// assert!(RadixRule::from_regex(r"{[0-9}").is_err());  // missing ]
152    /// assert!(RadixRule::from_regex(r"{:(0}").is_err());   // missing )
153    /// assert!(RadixRule::from_regex(r"{id:(0}").is_err()); // missing )
154    ///
155    /// #[allow(invalid_from_utf8_unchecked)]
156    /// let invalid = format!("{{{}}}", unsafe { std::str::from_utf8_unchecked(&[0xffu8, 0xfe, 0x65]) });
157    /// assert!(RadixRule::from_regex(invalid).is_err());
158    /// ```
159    #[inline]
160    pub fn from_regex(frag: impl Into<Bytes>) -> RadixResult<Self> {
161        let frag = frag.into();
162
163        if !frag.starts_with(b"{") || !frag.ends_with(b"}") {
164            return Err(RadixError::PathMalformed("regex lack of curly braces"));
165        }
166
167        let data = frag.slice(1..frag.len() - 1);
168        let find = match memchr::memchr(b':', data.as_ref()) {
169            Some(pos) => (data.slice(..pos), from_utf8(&data[pos + 1..])?),
170            None => (Bytes::new(), from_utf8(data.as_ref())?)
171        };
172
173        // regex must match from the beginning, add ^ if needed
174        let (name, expr) = match find.1.as_bytes().first() {
175            Some(b'^') => (find.0, Regex::new(find.1)?),
176            _ => (find.0, Regex::new(('^'.to_string() + find.1).as_str())?)
177        };
178
179        Ok(Self::Regex { frag, name, expr })
180    }
181
182    /// Check if the rule is plain text
183    ///
184    /// # Examples
185    ///
186    /// ```
187    /// use radixmap::{rule::RadixRule, RadixResult};
188    ///
189    /// fn main() -> RadixResult<()> {
190    ///     assert_eq!(RadixRule::from_plain("")?.is_plain(), true);
191    ///     assert_eq!(RadixRule::from_param(":id")?.is_plain(), false);
192    ///     assert_eq!(RadixRule::from_glob("*")?.is_plain(), false);
193    ///     assert_eq!(RadixRule::from_regex(r"{id:\d+}")?.is_plain(), false);
194    ///
195    ///     Ok(())
196    /// }
197    /// ```
198    #[inline]
199    pub fn is_plain(&self) -> bool {
200        matches!(self, RadixRule::Plain { .. })
201    }
202
203    /// Check if the rule is special
204    ///
205    /// # Examples
206    ///
207    /// ```
208    /// use radixmap::{rule::RadixRule, RadixResult};
209    ///
210    /// fn main() -> RadixResult<()> {
211    ///     assert_eq!(RadixRule::from_plain("")?.is_special(), false);
212    ///     assert_eq!(RadixRule::from_param(":id")?.is_special(), true);
213    ///     assert_eq!(RadixRule::from_glob("*")?.is_special(), true);
214    ///     assert_eq!(RadixRule::from_regex(r"{id:\d+}")?.is_special(), true);
215    ///
216    ///     Ok(())
217    /// }
218    /// ```
219    #[inline]
220    pub fn is_special(&self) -> bool {
221        !self.is_plain()
222    }
223
224    /// Match the path to find the longest shared segment
225    ///
226    /// # Examples
227    ///
228    /// ```
229    /// use radixmap::{rule::RadixRule, RadixResult};
230    ///
231    /// fn main() -> RadixResult<()> {
232    ///     assert_eq!(RadixRule::from_plain("")?.longest(b"", false), Some("".as_bytes()));
233    ///     assert_eq!(RadixRule::from_plain("")?.longest(b"api", false), Some("".as_bytes()));
234    ///     assert_eq!(RadixRule::from_plain("api")?.longest(b"api", false), Some("api".as_bytes()));
235    ///     assert_eq!(RadixRule::from_plain("api/v1")?.longest(b"api", false), Some("api".as_bytes()));
236    ///     assert_eq!(RadixRule::from_plain("api/v1")?.longest(b"api/v2", false), Some("api/v".as_bytes()));
237    ///     assert_eq!(RadixRule::from_plain("roadmap/issues/events/6430295168")?.longest(b"roadmap/issues/events/6635165802", false), Some("roadmap/issues/events/6".as_bytes()));
238    ///
239    ///     assert_eq!(RadixRule::from_param(":")?.longest(b"12345/rest", false), Some("12345".as_bytes()));
240    ///     assert_eq!(RadixRule::from_param(":id")?.longest(b"12345/rest", false), Some("12345".as_bytes()));
241    ///     assert_eq!(RadixRule::from_param(":id")?.longest(b"12345/rest", true), Some("".as_bytes()));
242    ///     assert_eq!(RadixRule::from_param(":id")?.longest(b":id", true), Some(":id".as_bytes()));
243    ///
244    ///     assert_eq!(RadixRule::from_glob("*")?.longest(b"12345/rest", false), Some("12345/rest".as_bytes()));
245    ///     assert_eq!(RadixRule::from_glob("*id")?.longest(b"12345/rest", false), None);
246    ///     assert_eq!(RadixRule::from_glob("*id")?.longest(b"12345/rest", true), Some("".as_bytes()));
247    ///     assert_eq!(RadixRule::from_glob("*id")?.longest(b"*id", true), Some("*id".as_bytes()));
248    ///
249    ///     assert_eq!(RadixRule::from_regex(r"{}")?.longest(b"12345/rest", false), Some(r"".as_bytes()));
250    ///     assert_eq!(RadixRule::from_regex(r"{:}")?.longest(b"12345/rest", false), Some(r"".as_bytes()));
251    ///     assert_eq!(RadixRule::from_regex(r"{\d+}")?.longest(b"12345/rest", false), Some(r"12345".as_bytes()));
252    ///     assert_eq!(RadixRule::from_regex(r"{:\d+}")?.longest(b"12345/rest", false), Some(r"12345".as_bytes()));
253    ///     assert_eq!(RadixRule::from_regex(r"{id:\d+}")?.longest(b"12345/update", false), Some(r"12345".as_bytes()));
254    ///     assert_eq!(RadixRule::from_regex(r"{id:\d+}")?.longest(b"abcde", false), None);
255    ///     assert_eq!(RadixRule::from_regex(r"{id:\d+}")?.longest(b"abcde", true), Some(r"".as_bytes()));
256    ///     assert_eq!(RadixRule::from_regex(r"{id:\d+}")?.longest(br"{id:\d+}", true), Some(r"{id:\d+}".as_bytes()));
257    ///
258    ///     Ok(())
259    /// }
260    /// ```
261    #[inline]
262    pub fn longest<'u>(&self, path: &'u [u8], raw: bool) -> Option<&'u [u8]> {
263        if matches!(self, RadixRule::Plain { .. }) || raw {
264            let frag = match self {
265                RadixRule::Plain { frag, .. } => frag,
266                RadixRule::Param { frag, .. } => frag,
267                RadixRule::Glob { frag, .. } => frag,
268                RadixRule::Regex { frag, .. } => frag,
269            };
270
271            // accelerating string comparison using numbers
272            let min = std::cmp::min(frag.len(), path.len());
273            let mut len = 0;
274
275            const BLK: usize = std::mem::size_of::<usize>();
276
277            while len + BLK <= min {
278                let frag_chunk: &usize = unsafe { &*(frag.as_ptr().add(len) as *const usize) };
279                let path_chunk: &usize = unsafe { &*(path.as_ptr().add(len) as *const usize) };
280
281                match frag_chunk == path_chunk {
282                    true => len += BLK,
283                    false => break,
284                }
285            }
286
287            // process the leftover unmatched substring
288            while len < min && frag[len] == path[len] {
289                len += 1;
290            }
291
292            return Some(&path[..len]);
293        }
294
295        match self {
296            RadixRule::Param { .. } => match memchr::memchr(b'/', path) {
297                Some(p) => Some(&path[..p]),
298                None if !path.is_empty() => Some(path),
299                None => None
300            }
301            RadixRule::Glob { glob, .. } => {
302                let utf8 = match from_utf8(path) {
303                    Ok(p) => p,
304                    Err(_) => return None,
305                };
306
307                match glob.matches(utf8) {
308                    true => Some(path),
309                    false => None
310                }
311            }
312            RadixRule::Regex { expr, .. } => {
313                let utf8 = match from_utf8(path) {
314                    Ok(p) => p,
315                    Err(_) => return None,
316                };
317
318                match expr.find(utf8) {
319                    Some(m) => Some(&path[..m.len()]),
320                    None => None
321                }
322            }
323            RadixRule::Plain { .. } => unreachable!(),
324        }
325    }
326
327    /// Divide the rule into two parts
328    ///
329    /// # Examples
330    ///
331    /// ```
332    /// use radixmap::{rule::RadixRule, RadixResult};
333    ///
334    /// fn main() -> RadixResult<()> {
335    ///     let mut rule = RadixRule::from_plain("/api")?;
336    ///
337    ///     assert_eq!(rule.divide(1)?, "api");
338    ///     assert_eq!(rule, "/");
339    ///
340    ///     assert!(RadixRule::from_param(":id")?.divide(1).is_err());
341    ///     assert!(RadixRule::from_glob("*")?.divide(1).is_err());
342    ///     assert!(RadixRule::from_regex(r"{id:\d+}")?.divide(1).is_err());
343    ///
344    ///     Ok(())
345    /// }
346    /// ```
347    #[inline]
348    pub fn divide(&mut self, len: usize) -> RadixResult<RadixRule> {
349        match self {
350            RadixRule::Plain { frag } if frag.len() > len => {
351                let rule = RadixRule::from_plain(frag.slice(len..));
352                *frag = frag.slice(..len);
353                rule
354            }
355            _ => Err(RadixError::RuleIndivisible)
356        }
357    }
358
359    /// Origin fragment of the rule
360    ///
361    /// # Examples
362    ///
363    /// ```
364    /// use radixmap::{rule::RadixRule, RadixResult};
365    ///
366    /// fn main() -> RadixResult<()> {
367    ///     assert_eq!(RadixRule::from_plain("/api")?.origin(), "/api");
368    ///     assert_eq!(RadixRule::from_param(":id")?.origin(), ":id");
369    ///     assert_eq!(RadixRule::from_glob("*")?.origin(), "*");
370    ///     assert_eq!(RadixRule::from_regex(r"{id:\d+}")?.origin(), r"{id:\d+}");
371    ///
372    ///     Ok(())
373    /// }
374    /// ```
375    #[inline]
376    pub fn origin(&self) -> &Bytes {
377        match self {
378            RadixRule::Plain { frag } => frag,
379            RadixRule::Param { frag, .. } => frag,
380            RadixRule::Glob { frag, .. } => frag,
381            RadixRule::Regex { frag, .. } => frag,
382        }
383    }
384
385    /// The name of the named param and regex
386    ///
387    /// # Examples
388    ///
389    /// ```
390    /// use radixmap::{rule::RadixRule, RadixResult};
391    ///
392    /// fn main() -> RadixResult<()> {
393    ///     assert_eq!(RadixRule::from_param(":id")?.identity(), "id");
394    ///     assert_eq!(RadixRule::from_regex(r"{id:\d+}")?.identity(), r"id");
395    ///
396    ///     assert_eq!(RadixRule::from_plain("/api")?.identity(), "");
397    ///     assert_eq!(RadixRule::from_param(":")?.identity(), "");
398    ///     assert_eq!(RadixRule::from_glob("*")?.identity(), "*");
399    ///     assert_eq!(RadixRule::from_regex(r"{\d+}")?.identity(), r"");
400    ///
401    ///     Ok(())
402    /// }
403    /// ```
404    #[inline]
405    pub fn identity(&self) -> &Bytes {
406        static EMPTY: Bytes = Bytes::new();
407        static GLOB: Bytes = Bytes::from_static(b"*");
408
409        match self {
410            RadixRule::Plain { .. } => &EMPTY,
411            RadixRule::Param { name, .. } => name,
412            RadixRule::Glob { .. } => &GLOB,
413            RadixRule::Regex { name, .. } => name,
414        }
415    }
416}
417
418/// Analyze a path as long as possible and construct a rule
419///
420/// # Examples
421///
422/// ```
423/// use radixmap::{rule::RadixRule, RadixResult};
424///
425/// fn main() -> RadixResult<()> {
426///     assert!(RadixRule::try_from("").is_err());
427///
428///     assert_eq!(RadixRule::try_from("api")?, "api");
429///     assert_eq!(RadixRule::try_from("api/v1")?, "api/v1");
430///     assert_eq!(RadixRule::try_from("/api/v1")?, "/api/v1");
431///
432///     assert_eq!(RadixRule::try_from(":")?, ":");
433///     assert_eq!(RadixRule::try_from(":id")?, ":id");
434///     assert_eq!(RadixRule::try_from(":id/rest")?, ":id");
435///
436///     assert_eq!(RadixRule::try_from("*")?, "*");
437///     assert_eq!(RadixRule::try_from("*rest")?, "*rest");
438///     assert_eq!(RadixRule::try_from("*/rest")?, "*/rest");
439///
440///     assert_eq!(RadixRule::try_from(r"{id:\d+}")?, r"{id:\d+}");
441///     assert_eq!(RadixRule::try_from(r"{id:\d+}/rest")?, r"{id:\d+}");
442///     assert!(RadixRule::try_from(r"{id:\d+").is_err());
443///     assert!(RadixRule::try_from(r"{id:\d+/rest").is_err());
444///
445///     Ok(())
446/// }
447/// ```
448impl TryFrom<Bytes> for RadixRule {
449    type Error = RadixError;
450
451    fn try_from(path: Bytes) -> Result<Self, Self::Error> {
452        let init = path.first().ok_or(RadixError::PathEmpty)?;
453
454        match *init {
455            b':' => match memchr::memchr(b'/', path.as_ref()) {
456                Some(pos) => Self::from_param(path.slice(..pos)),
457                _ => Self::from_param(path),
458            }
459            b'*' => {
460                Self::from_glob(path)
461            }
462            b'{' => match memchr::memchr(b'}', path.as_ref()) {
463                Some(pos) => Self::from_regex(path.slice(..pos + 1)),
464                _ => Err(RadixError::PathMalformed("missing closing sign '}'"))
465            }
466            _ => match memchr::memchr3(b'{', b':', b'*', path.as_ref()) {
467                Some(pos) => Self::from_plain(path.slice(..pos)),
468                None => Self::from_plain(path),
469            }
470        }
471    }
472}
473
474/// Analyze a path as long as possible and construct a rule
475impl TryFrom<&'static [u8]> for RadixRule {
476    type Error = RadixError;
477
478    fn try_from(path: &'static [u8]) -> Result<Self, Self::Error> {
479        Bytes::from(path).try_into()
480    }
481}
482
483/// Analyze a path as long as possible and construct a rule
484impl TryFrom<&'static str> for RadixRule {
485    type Error = RadixError;
486
487    fn try_from(path: &'static str) -> Result<Self, Self::Error> {
488        Bytes::from(path).try_into()
489    }
490}
491
492/// Default trait
493///
494/// # Examples
495///
496/// ```
497/// use radixmap::{rule::RadixRule};
498///
499/// assert_eq!(RadixRule::default(), "");
500/// ```
501impl Default for RadixRule {
502    #[inline]
503    fn default() -> Self {
504        Self::Plain { frag: Bytes::new() }
505    }
506}
507
508/// Debug trait
509///
510/// # Examples
511///
512/// ```
513/// use radixmap::{rule::RadixRule, RadixResult};
514///
515/// fn main() -> RadixResult<()> {
516///     assert_eq!(format!("{:?}", RadixRule::from_plain("/api")?).as_str(), "Plain(/api)");
517///     assert_eq!(format!("{:?}", RadixRule::from_param(":id")?).as_str(), "Param(:id)");
518///     assert_eq!(format!("{:?}", RadixRule::from_glob("*")?).as_str(), "Glob(*)");
519///     assert_eq!(format!("{:?}", RadixRule::from_regex(r"{id:\d+}")?).as_str(), r"Regex({id:\d+})");
520///
521///     Ok(())
522/// }
523/// ```
524impl Debug for RadixRule {
525    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
526        let (kind, frag) = match self {
527            RadixRule::Plain { frag } => ("Plain", frag),
528            RadixRule::Param { frag, .. } => ("Param", frag),
529            RadixRule::Glob { frag, .. } => ("Glob", frag),
530            RadixRule::Regex { frag, .. } => ("Regex", frag),
531        };
532
533        write!(f, "{}({})", kind, unsafe { from_utf8_unchecked(frag.as_ref()) })
534    }
535}
536
537/// Hash trait
538///
539/// # Examples
540///
541/// ```
542/// use std::collections::HashMap;
543/// use radixmap::{rule::RadixRule, RadixResult};
544///
545/// fn main() -> RadixResult<()> {
546///     let mut map = HashMap::new();
547///     map.insert(RadixRule::from_plain("/api")?, "/api");
548///     map.insert(RadixRule::from_param(":id")?, ":id");
549///     map.insert(RadixRule::from_glob("*")?, "*");
550///     map.insert(RadixRule::from_regex(r"{id:\d+}")?, r"{id:\d+}");
551///
552///     assert_eq!(map[&RadixRule::from_plain("/api")?], "/api");
553///     assert_eq!(map[&RadixRule::from_param(":id")?], ":id");
554///     assert_eq!(map[&RadixRule::from_glob("*")?], "*");
555///     assert_eq!(map[&RadixRule::from_regex(r"{id:\d+}")?], r"{id:\d+}");
556///
557///     Ok(())
558/// }
559/// ```
560impl Hash for RadixRule {
561    #[inline]
562    fn hash<H: Hasher>(&self, state: &mut H) {
563        match self {
564            RadixRule::Plain { frag } => {
565                "Plain".hash(state);
566                frag.hash(state);
567            }
568            RadixRule::Param { frag, .. } => {
569                "Param".hash(state);
570                frag.hash(state);
571            }
572            RadixRule::Glob { frag, .. } => {
573                "Glob".hash(state);
574                frag.hash(state);
575            }
576            RadixRule::Regex { frag, .. } => {
577                "Regex".hash(state);
578                frag.hash(state);
579            }
580        }
581    }
582}
583
584/// == & !=
585impl Eq for RadixRule {}
586
587/// == & !=
588///
589/// # Examples
590///
591/// ```
592/// use radixmap::{rule::RadixRule, RadixResult};
593///
594/// fn main() -> RadixResult<()> {
595///     assert_eq!(RadixRule::from_plain("/api")?, RadixRule::from_plain("/api")?);
596///     assert_eq!(RadixRule::from_param(":id")?, RadixRule::from_param(":id")?);
597///     assert_eq!(RadixRule::from_glob("*")?, RadixRule::from_glob("*")?);
598///     assert_eq!(RadixRule::from_regex(r"{id:\d+}")?, RadixRule::from_regex(r"{id:\d+}")?);
599///
600///     assert_ne!(RadixRule::from_plain("/api")?, RadixRule::from_plain("")?);
601///     assert_ne!(RadixRule::from_param(":id")?, RadixRule::from_param(":")?);
602///     assert_ne!(RadixRule::from_glob("*")?, RadixRule::from_glob("**")?);
603///     assert_ne!(RadixRule::from_regex(r"{id:\d+}")?, RadixRule::from_regex(r"{}")?);
604///
605///     // type mismatch
606///     assert_ne!(RadixRule::from_plain("{}")?, RadixRule::from_regex(r"{}")?);
607///
608///     Ok(())
609/// }
610/// ```
611impl PartialEq for RadixRule {
612    #[inline]
613    fn eq(&self, other: &Self) -> bool {
614        match (self, other) {
615            (RadixRule::Plain { frag: a }, RadixRule::Plain { frag: b }) => a == b,
616            (RadixRule::Param { frag: a, .. }, RadixRule::Param { frag: b, .. }) => a == b,
617            (RadixRule::Glob { frag: a, .. }, RadixRule::Glob { frag: b, .. }) => a == b,
618            (RadixRule::Regex { frag: a, .. }, RadixRule::Regex { frag: b, .. }) => a == b,
619            _ => false
620        }
621    }
622}
623
624/// == & !=
625///
626/// # Examples
627///
628/// ```
629/// use radixmap::{rule::RadixRule, RadixResult};
630///
631/// fn main() -> RadixResult<()> {
632///     assert_eq!(RadixRule::from_plain("/api")?, "/api");
633///     assert_eq!(RadixRule::from_param(":id")?, ":id");
634///     assert_eq!(RadixRule::from_glob("*")?, "*");
635///     assert_eq!(RadixRule::from_regex(r"{id:\d+}")?, r"{id:\d+}");
636///
637///     assert_ne!(RadixRule::from_plain("/api")?, "");
638///     assert_ne!(RadixRule::from_param(":id")?, ":");
639///     assert_ne!(RadixRule::from_glob("*")?, "**");
640///     assert_ne!(RadixRule::from_regex(r"{id:\d+}")?, r"{}");
641///
642///     Ok(())
643/// }
644/// ```
645impl PartialEq<&[u8]> for RadixRule {
646    #[inline]
647    fn eq(&self, other: &&[u8]) -> bool {
648        self.origin() == *other
649    }
650}
651
652/// == & !=
653impl<const N: usize> PartialEq<&[u8; N]> for RadixRule {
654    #[inline]
655    fn eq(&self, other: &&[u8; N]) -> bool {
656        self.origin() == other.as_ref()
657    }
658}
659
660/// == & !=
661impl PartialEq<&str> for RadixRule {
662    #[inline]
663    fn eq(&self, other: &&str) -> bool {
664        self.origin() == other.as_bytes()
665    }
666}