diff --git a/CHANGELOG.md b/CHANGELOG.md index 18e83a7..f9686e3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Adds method `into_buf` for `Box` and `impl From for Box`. - Adds unsafe associated methods `Pointer::new_unchecked` and `PointerBuf::new_unchecked` for external zero-cost construction. +- Adds `Pointer::starts_with` and `Pointer::ends_with` for prefix and suffix matching. - Adds new `ParseIndexError` variant to express the presence non-digit characters in the token. - Adds `Token::is_next` for checking if a token represents the `-` character. diff --git a/src/pointer.rs b/src/pointer.rs index 8b1f0f7..8e04bb5 100644 --- a/src/pointer.rs +++ b/src/pointer.rs @@ -280,12 +280,26 @@ impl Pointer { .map(|s| unsafe { Self::new_unchecked(s) }) } - /// Attempts to get a `Token` or a segment of the `Pointer`, depending on - /// the type of index. + /// Returns whether `self` has a suffix of `other`. /// - /// Returns `None` if the index is out of bounds. + /// Note that `Pointer::root` is only a valid suffix of itself. + pub fn ends_with(&self, other: &Self) -> bool { + (self.is_root() && other.is_root()) + || (!other.is_root() && self.as_str().ends_with(&other.0)) + } + + /// Returns whether `self` has a prefix of `other.` /// - /// Note that this operation is O(n). + /// Note that `Pointer::root` is a valid prefix of any `Pointer` (including + /// itself). + pub fn starts_with(&self, other: &Self) -> bool { + self.as_str().starts_with(&other.0) + // ensure we end at a token boundary + && (other.len() == self.len() || self.0.as_bytes()[other.len()] == b'/') + } + + /// Attempts to get a `Token` by the index. Returns `None` if the index is + /// out of bounds. /// /// ## Example /// ```rust @@ -1372,6 +1386,58 @@ mod tests { assert_eq!(stripped, "/to/some/value"); } + #[test] + fn ends_with() { + // positive cases + let p = Pointer::from_static("/foo/bar"); + let q = Pointer::from_static("/bar"); + assert!(p.ends_with(q)); + let q = Pointer::from_static("/foo/bar"); + assert!(p.ends_with(q)); + + // negative cases + let q = Pointer::from_static("/barz"); + assert!(!p.ends_with(q)); + let q = Pointer::from_static("/"); + assert!(!p.ends_with(q)); + let q = Pointer::from_static(""); + assert!(!p.ends_with(q)); + let q = Pointer::from_static("/qux/foo/bar"); + assert!(!p.ends_with(q)); + + // edge case - both root + let p = Pointer::root(); + let q = Pointer::root(); + assert!(p.ends_with(q)); + } + + #[test] + fn starts_with() { + // positive cases + let p = Pointer::from_static("/foo/bar"); + let q = Pointer::from_static("/foo"); + assert!(p.starts_with(q)); + let q = Pointer::from_static("/foo/bar"); + assert!(p.starts_with(q)); + + // negative cases + let q = Pointer::from_static("/"); + assert!(!p.starts_with(q)); + let q = Pointer::from_static("/fo"); + assert!(!p.starts_with(q)); + let q = Pointer::from_static("/foo/"); + assert!(!p.starts_with(q)); + + // edge cases: other is root + let p = Pointer::root(); + let q = Pointer::root(); + assert!(p.starts_with(q)); + let p = Pointer::from_static("/"); + assert!(p.starts_with(q)); + let p = Pointer::from_static("/any/thing"); + assert!(p.starts_with(q)); + } + #[test] fn parse_error_is_no_leading_backslash() { let err = ParseError::NoLeadingBackslash;