Skip to main content

exoware_sdk/
selector.rs

1//! Shared key-filter shape: `(prefix, payload_regex)`.
2//!
3//! Used by both `prune_policy` (prune service) and `stream_filter` (stream
4//! service) so one domain type round-trips through the `common.kv.v1.Selector`
5//! proto message and one regex compiler handles validation everywhere.
6
7use anyhow::{ensure, Context};
8use bytes::{Buf, BufMut, Bytes};
9use commonware_codec::{EncodeSize, Error as CodecError, RangeCfg, Read, ReadExt, Write};
10use regex::bytes::Regex;
11
12use crate::keys::MAX_KEY_LEN;
13use crate::kv_codec::Utf8;
14
15/// Identifies a subset of keys by a byte prefix + payload regex. Matches the
16/// `common.kv.v1.Selector` wire shape (see `proto/common/v1/kv.proto`). A key
17/// belongs to the selector's family iff it starts with `prefix`; the payload
18/// (the key bytes after `prefix`) is matched against `payload_regex`. An empty
19/// `prefix` matches all keys.
20#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
21pub struct Selector {
22    pub prefix: Bytes,
23    pub payload_regex: Utf8,
24}
25
26impl Write for Selector {
27    fn write(&self, buf: &mut impl BufMut) {
28        self.prefix.as_ref().write(buf);
29        self.payload_regex.write(buf);
30    }
31}
32
33impl EncodeSize for Selector {
34    fn encode_size(&self) -> usize {
35        self.prefix.as_ref().encode_size() + self.payload_regex.encode_size()
36    }
37}
38
39impl Read for Selector {
40    type Cfg = ();
41    fn read_cfg(buf: &mut impl Buf, _: &()) -> Result<Self, CodecError> {
42        let range: RangeCfg<usize> = (..=MAX_KEY_LEN).into();
43        let prefix = Vec::<u8>::read_cfg(buf, &(range, ()))?;
44        Ok(Selector {
45            prefix: Bytes::from(prefix),
46            payload_regex: Utf8::read(buf)?,
47        })
48    }
49}
50
51/// Compile the payload regex, rejecting empty / blank strings. Bytes-regex
52/// because keys carry arbitrary non-UTF8 payloads.
53pub fn compile_payload_regex(raw: &str) -> anyhow::Result<Regex> {
54    ensure!(
55        !raw.trim().is_empty(),
56        "selector payload_regex must not be empty"
57    );
58    Regex::new(raw).with_context(|| format!("invalid selector payload_regex {raw:?}"))
59}