urlpattern/
matcher.rs

1use crate::regexp::RegExp;
2use crate::Error;
3
4#[derive(Debug)]
5/// A structured representation of a URLPattern matcher, which can be used to
6/// match a URL against a pattern quickly.
7pub(crate) struct Matcher<R: RegExp> {
8  pub prefix: String,
9  pub suffix: String,
10  pub inner: InnerMatcher<R>,
11  pub ignore_case: bool,
12}
13
14#[derive(Debug)]
15pub(crate) enum InnerMatcher<R: RegExp> {
16  /// A literal string matcher.
17  ///
18  /// # Examples
19  /// - /
20  /// - /foo
21  Literal { literal: String },
22  /// A matcher that matches all chars, except the substring specified in
23  /// `filter` (if it is set).
24  ///
25  /// # Examples
26  /// - *
27  /// - /old/*
28  /// - /scripts/*.js
29  /// - /:slug
30  /// - /blog/:id
31  /// - /blog/:id.html
32  SingleCapture {
33    filter: Option<char>,
34    allow_empty: bool,
35  },
36  /// A regexp matcher. This is a bail-out matcher for arbitrary complexity
37  /// matchers.
38  ///
39  /// # Examples
40  /// - /foo/:id?
41  RegExp { regexp: Result<R, Error> },
42}
43
44impl<R: RegExp> Matcher<R> {
45  pub fn matches<'a>(
46    &self,
47    mut input: &'a str,
48  ) -> Option<Vec<Option<&'a str>>> {
49    let prefix_len = self.prefix.len();
50    let suffix_len = self.suffix.len();
51    let input_len = input.len();
52    if prefix_len + suffix_len > 0 {
53      // The input must be at least as long as the prefix and suffix combined,
54      // because these must both be present, and not overlap.
55      if input_len < prefix_len + suffix_len {
56        return None;
57      }
58      if !input.starts_with(&self.prefix) {
59        return None;
60      }
61      if !input.ends_with(&self.suffix) {
62        return None;
63      }
64      input = &input[prefix_len..input_len - suffix_len];
65    }
66
67    match &self.inner {
68      InnerMatcher::Literal { literal } => {
69        if self.ignore_case {
70          (input.to_lowercase() == literal.to_lowercase()).then(Vec::new)
71        } else {
72          (input == literal).then(Vec::new)
73        }
74      }
75      InnerMatcher::SingleCapture {
76        filter,
77        allow_empty,
78      } => {
79        if input.is_empty() && !allow_empty {
80          return None;
81        }
82        if let Some(filter) = filter {
83          if self.ignore_case {
84            if input
85              .to_lowercase()
86              .contains(filter.to_lowercase().collect::<Vec<_>>().as_slice())
87            {
88              return None;
89            }
90          } else if input.contains(*filter) {
91            return None;
92          }
93        }
94        Some(vec![Some(input)])
95      }
96      InnerMatcher::RegExp { regexp, .. } => {
97        regexp.as_ref().unwrap().matches(input)
98      }
99    }
100  }
101}