//! Key matching utilities for watch subscriptions /// Key matcher for watch subscriptions #[derive(Debug, Clone)] pub struct KeyMatcher { /// Start key key: Vec, /// Range end (exclusive). None = single key match range_end: Option>, } impl KeyMatcher { /// Create a matcher for a single key pub fn key(key: impl Into>) -> Self { Self { key: key.into(), range_end: None, } } /// Create a matcher for a key range pub fn range(key: impl Into>, range_end: impl Into>) -> Self { Self { key: key.into(), range_end: Some(range_end.into()), } } /// Create a matcher for all keys with a given prefix pub fn prefix(prefix: impl Into>) -> Self { let prefix = prefix.into(); let range_end = prefix_end(&prefix); Self { key: prefix, range_end: Some(range_end), } } /// Create a matcher for all keys pub fn all() -> Self { Self { key: vec![0], range_end: Some(vec![]), } } /// Check if a key matches this matcher pub fn matches(&self, target: &[u8]) -> bool { match &self.range_end { None => self.key == target, Some(end) => { if end.is_empty() { // Empty end means all keys >= start target >= self.key.as_slice() } else { target >= self.key.as_slice() && target < end.as_slice() } } } } /// Get the start key pub fn start_key(&self) -> &[u8] { &self.key } /// Get the range end pub fn range_end(&self) -> Option<&[u8]> { self.range_end.as_deref() } /// Check if this is a single key match pub fn is_single_key(&self) -> bool { self.range_end.is_none() } /// Check if this is a prefix match pub fn is_prefix(&self) -> bool { self.range_end.is_some() } } /// Calculate the end key for a prefix scan /// For prefix "abc", returns "abd" (increment last byte) fn prefix_end(prefix: &[u8]) -> Vec { let mut end = prefix.to_vec(); for i in (0..end.len()).rev() { if end[i] < 0xff { end[i] += 1; end.truncate(i + 1); return end; } } // All bytes are 0xff, return empty to indicate no upper bound Vec::new() } #[cfg(test)] mod tests { use super::*; #[test] fn test_single_key_match() { let matcher = KeyMatcher::key(b"/nodes/1"); assert!(matcher.matches(b"/nodes/1")); assert!(!matcher.matches(b"/nodes/2")); assert!(!matcher.matches(b"/nodes/10")); assert!(!matcher.matches(b"/nodes")); } #[test] fn test_prefix_match() { let matcher = KeyMatcher::prefix(b"/nodes/"); assert!(matcher.matches(b"/nodes/1")); assert!(matcher.matches(b"/nodes/abc")); assert!(matcher.matches(b"/nodes/")); assert!(!matcher.matches(b"/nodes")); assert!(!matcher.matches(b"/tasks/1")); } #[test] fn test_range_match() { let matcher = KeyMatcher::range(b"a", b"d"); assert!(matcher.matches(b"a")); assert!(matcher.matches(b"b")); assert!(matcher.matches(b"c")); assert!(!matcher.matches(b"d")); // End is exclusive assert!(!matcher.matches(b"e")); } #[test] fn test_all_match() { let matcher = KeyMatcher::all(); assert!(matcher.matches(b"any")); assert!(matcher.matches(b"/path/to/key")); assert!(matcher.matches(b"\xff\xff\xff")); } #[test] fn test_prefix_end() { assert_eq!(prefix_end(b"abc"), b"abd"); assert_eq!(prefix_end(b"ab\xff"), b"ac"); assert_eq!(prefix_end(b"a\xff\xff"), b"b"); assert_eq!(prefix_end(b"\xff\xff"), Vec::::new()); } }