photoncloud-monorepo/chainfire/crates/chainfire-watch/src/matcher.rs
centra 8f94aee1fa Fix R8: Convert submodule gitlinks to regular directories
- Remove gitlinks (160000 mode) for chainfire, flaredb, iam
- Add workspace contents as regular tracked files
- Update flake.nix to use simple paths instead of builtins.fetchGit

This resolves the nix build failure where submodule directories
appeared empty in the nix store.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-09 16:51:20 +09:00

150 lines
3.9 KiB
Rust

//! Key matching utilities for watch subscriptions
/// Key matcher for watch subscriptions
#[derive(Debug, Clone)]
pub struct KeyMatcher {
/// Start key
key: Vec<u8>,
/// Range end (exclusive). None = single key match
range_end: Option<Vec<u8>>,
}
impl KeyMatcher {
/// Create a matcher for a single key
pub fn key(key: impl Into<Vec<u8>>) -> Self {
Self {
key: key.into(),
range_end: None,
}
}
/// Create a matcher for a key range
pub fn range(key: impl Into<Vec<u8>>, range_end: impl Into<Vec<u8>>) -> 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<Vec<u8>>) -> 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<u8> {
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::<u8>::new());
}
}