Skip to main content

json_traits/
path_pattern.rs

1/// Matches dotted paths against patterns.
2///
3/// A pattern segment of `*` matches exactly one path segment.
4/// `contacts.*.info.name` matches `contacts.0.info.name` and does not match
5/// `contacts.0.info.name.last`.
6///
7/// These methods correspond to `IsSupportedBy` and `IsAPathMatchWith`.
8/// Call [`PathPattern::is_supported_by`] on a path, and
9/// [`PathPattern::is_a_path_match_with`] on a pattern.
10pub trait PathPattern {
11    /// `true` when this path matches any of `patterns`.
12    ///
13    /// # Examples
14    ///
15    /// ```
16    /// use json_traits::PathPattern;
17    ///
18    /// let patterns = ["prop1.prop2", "contacts.*.info.name", "contacts.*.info.number"];
19    /// assert!("contacts.0.info.name".is_supported_by(patterns));
20    /// assert!(!"contacts.0.info.name.last".is_supported_by(patterns));
21    /// ```
22    #[doc(alias = "IsSupportedBy")]
23    #[must_use]
24    fn is_supported_by<I, S>(&self, patterns: I) -> bool
25    where
26        I: IntoIterator<Item = S>,
27        S: AsRef<str>;
28
29    /// `true` when this pattern matches `path`.
30    ///
31    /// The receiver is the pattern: `"contacts.*.name".is_a_path_match_with("contacts.0.name")`.
32    #[doc(alias = "IsAPathMatchWith")]
33    #[must_use]
34    fn is_a_path_match_with(&self, path: &str) -> bool;
35}
36
37impl PathPattern for str {
38    fn is_supported_by<I, S>(&self, patterns: I) -> bool
39    where
40        I: IntoIterator<Item = S>,
41        S: AsRef<str>,
42    {
43        patterns
44            .into_iter()
45            .any(|pattern| pattern.as_ref().is_a_path_match_with(self))
46    }
47
48    fn is_a_path_match_with(&self, path: &str) -> bool {
49        if !self.as_bytes().contains(&b'*') {
50            return self == path;
51        }
52        segments_match(self.as_bytes(), path.as_bytes())
53    }
54}
55
56fn segments_match(mut pattern: &[u8], mut path: &[u8]) -> bool {
57    loop {
58        let (pattern_segment, pattern_rest) = split_segment(pattern);
59        let (path_segment, path_rest) = split_segment(path);
60        if pattern_segment != b"*" && pattern_segment != path_segment {
61            return false;
62        }
63        match (pattern_rest, path_rest) {
64            (None, None) => return true,
65            (Some(pattern_rest), Some(path_rest)) => {
66                pattern = pattern_rest;
67                path = path_rest;
68            }
69            _ => return false,
70        }
71    }
72}
73
74fn split_segment(input: &[u8]) -> (&[u8], Option<&[u8]>) {
75    match input.iter().position(|&byte| byte == b'.') {
76        Some(index) => (&input[..index], Some(&input[index + 1..])),
77        None => (input, None),
78    }
79}