- 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>
150 lines
3.9 KiB
Rust
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());
|
|
}
|
|
}
|