use std::collections::HashMap;
use std::sync::Arc;
pub type FilterResult<T> = Result<T, FilterError>;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum FilterError {
InvalidPredicate(String),
FieldNotFound(String),
TypeMismatch { expected: String, found: String },
ExecutionError(String),
PluginLoadError(String),
}
impl std::fmt::Display for FilterError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
FilterError::InvalidPredicate(s) => write!(f, "invalid predicate: {}", s),
FilterError::FieldNotFound(s) => write!(f, "field not found: {}", s),
FilterError::TypeMismatch { expected, found } => {
write!(f, "type mismatch: expected {}, found {}", expected, found)
}
FilterError::ExecutionError(s) => write!(f, "execution error: {}", s),
FilterError::PluginLoadError(s) => write!(f, "plugin load error: {}", s),
}
}
}
impl std::error::Error for FilterError {}
#[derive(Debug, Clone, PartialEq)]
pub enum Value {
Null,
Bool(bool),
Int(i64),
Float(f64),
String(String),
Bytes(Vec<u8>),
List(Vec<Value>),
Map(HashMap<String, Value>),
}
impl Value {
pub fn is_truthy(&self) -> bool {
match self {
Value::Null => false,
Value::Bool(b) => *b,
Value::Int(i) => *i != 0,
Value::Float(f) => *f != 0.0,
Value::String(s) => !s.is_empty(),
Value::Bytes(b) => !b.is_empty(),
Value::List(l) => !l.is_empty(),
Value::Map(m) => !m.is_empty(),
}
}
pub fn as_int(&self) -> Option<i64> {
match self {
Value::Int(i) => Some(*i),
Value::Float(f) => Some(*f as i64),
_ => None,
}
}
pub fn as_float(&self) -> Option<f64> {
match self {
Value::Float(f) => Some(*f),
Value::Int(i) => Some(*i as f64),
_ => None,
}
}
pub fn as_str(&self) -> Option<&str> {
match self {
Value::String(s) => Some(s),
_ => None,
}
}
}
pub trait Filterable {
fn get_field(&self, name: &str) -> Option<Value>;
fn id(&self) -> u64;
}
#[derive(Debug, Clone)]
pub struct Document {
id: u64,
fields: HashMap<String, Value>,
}
impl Document {
pub fn new(id: u64) -> Self {
Self {
id,
fields: HashMap::new(),
}
}
pub fn set_field(&mut self, name: impl Into<String>, value: Value) {
self.fields.insert(name.into(), value);
}
pub fn with_fields(id: u64, fields: HashMap<String, Value>) -> Self {
Self { id, fields }
}
}
impl Filterable for Document {
fn get_field(&self, name: &str) -> Option<Value> {
self.fields.get(name).cloned()
}
fn id(&self) -> u64 {
self.id
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CompareOp {
Eq,
Ne,
Lt,
Le,
Gt,
Ge,
Contains,
StartsWith,
EndsWith,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LogicalOp {
And,
Or,
Not,
}
#[derive(Debug, Clone)]
pub enum PredicateExpr {
Literal(Value),
Field(String),
Compare {
op: CompareOp,
left: Box<PredicateExpr>,
right: Box<PredicateExpr>,
},
Logical {
op: LogicalOp,
operands: Vec<PredicateExpr>,
},
In {
value: Box<PredicateExpr>,
list: Vec<PredicateExpr>,
},
Between {
value: Box<PredicateExpr>,
low: Box<PredicateExpr>,
high: Box<PredicateExpr>,
},
IsNull(Box<PredicateExpr>),
}
impl PredicateExpr {
pub fn eq(left: PredicateExpr, right: PredicateExpr) -> Self {
PredicateExpr::Compare {
op: CompareOp::Eq,
left: Box::new(left),
right: Box::new(right),
}
}
pub fn field(name: impl Into<String>) -> Self {
PredicateExpr::Field(name.into())
}
pub fn lit(value: Value) -> Self {
PredicateExpr::Literal(value)
}
pub fn and(operands: Vec<PredicateExpr>) -> Self {
PredicateExpr::Logical {
op: LogicalOp::And,
operands,
}
}
pub fn or(operands: Vec<PredicateExpr>) -> Self {
PredicateExpr::Logical {
op: LogicalOp::Or,
operands,
}
}
pub fn not(expr: PredicateExpr) -> Self {
PredicateExpr::Logical {
op: LogicalOp::Not,
operands: vec![expr],
}
}
}
pub struct PredicateEvaluator {
expr: PredicateExpr,
selectivity: f64,
}
impl PredicateEvaluator {
pub fn new(expr: PredicateExpr) -> Self {
let selectivity = Self::estimate_selectivity(&expr);
Self { expr, selectivity }
}
pub fn evaluate<D: Filterable>(&self, doc: &D) -> FilterResult<bool> {
self.eval_expr(&self.expr, doc).map(|v| v.is_truthy())
}
pub fn selectivity(&self) -> f64 {
self.selectivity
}
fn eval_expr<D: Filterable>(&self, expr: &PredicateExpr, doc: &D) -> FilterResult<Value> {
match expr {
PredicateExpr::Literal(v) => Ok(v.clone()),
PredicateExpr::Field(name) => doc
.get_field(name)
.ok_or_else(|| FilterError::FieldNotFound(name.clone())),
PredicateExpr::Compare { op, left, right } => {
let lval = self.eval_expr(left, doc)?;
let rval = self.eval_expr(right, doc)?;
Ok(Value::Bool(self.compare(*op, &lval, &rval)))
}
PredicateExpr::Logical { op, operands } => match op {
LogicalOp::And => {
for operand in operands {
let val = self.eval_expr(operand, doc)?;
if !val.is_truthy() {
return Ok(Value::Bool(false));
}
}
Ok(Value::Bool(true))
}
LogicalOp::Or => {
for operand in operands {
let val = self.eval_expr(operand, doc)?;
if val.is_truthy() {
return Ok(Value::Bool(true));
}
}
Ok(Value::Bool(false))
}
LogicalOp::Not => {
let val = self.eval_expr(&operands[0], doc)?;
Ok(Value::Bool(!val.is_truthy()))
}
},
PredicateExpr::In { value, list } => {
let val = self.eval_expr(value, doc)?;
for item in list {
let item_val = self.eval_expr(item, doc)?;
if self.compare(CompareOp::Eq, &val, &item_val) {
return Ok(Value::Bool(true));
}
}
Ok(Value::Bool(false))
}
PredicateExpr::Between { value, low, high } => {
let val = self.eval_expr(value, doc)?;
let low_val = self.eval_expr(low, doc)?;
let high_val = self.eval_expr(high, doc)?;
let ge_low = self.compare(CompareOp::Ge, &val, &low_val);
let le_high = self.compare(CompareOp::Le, &val, &high_val);
Ok(Value::Bool(ge_low && le_high))
}
PredicateExpr::IsNull(inner) => {
let val = self.eval_expr(inner, doc).unwrap_or(Value::Null);
Ok(Value::Bool(matches!(val, Value::Null)))
}
}
}
fn compare(&self, op: CompareOp, left: &Value, right: &Value) -> bool {
match op {
CompareOp::Eq => left == right,
CompareOp::Ne => left != right,
CompareOp::Lt => self.ord_compare(left, right) == Some(std::cmp::Ordering::Less),
CompareOp::Le => {
matches!(
self.ord_compare(left, right),
Some(std::cmp::Ordering::Less | std::cmp::Ordering::Equal)
)
}
CompareOp::Gt => self.ord_compare(left, right) == Some(std::cmp::Ordering::Greater),
CompareOp::Ge => {
matches!(
self.ord_compare(left, right),
Some(std::cmp::Ordering::Greater | std::cmp::Ordering::Equal)
)
}
CompareOp::Contains => self.string_contains(left, right),
CompareOp::StartsWith => self.string_starts_with(left, right),
CompareOp::EndsWith => self.string_ends_with(left, right),
}
}
fn ord_compare(&self, left: &Value, right: &Value) -> Option<std::cmp::Ordering> {
match (left, right) {
(Value::Int(a), Value::Int(b)) => Some(a.cmp(b)),
(Value::Float(a), Value::Float(b)) => a.partial_cmp(b),
(Value::Int(a), Value::Float(b)) => (*a as f64).partial_cmp(b),
(Value::Float(a), Value::Int(b)) => a.partial_cmp(&(*b as f64)),
(Value::String(a), Value::String(b)) => Some(a.cmp(b)),
_ => None,
}
}
fn string_contains(&self, left: &Value, right: &Value) -> bool {
match (left, right) {
(Value::String(a), Value::String(b)) => a.contains(b.as_str()),
_ => false,
}
}
fn string_starts_with(&self, left: &Value, right: &Value) -> bool {
match (left, right) {
(Value::String(a), Value::String(b)) => a.starts_with(b.as_str()),
_ => false,
}
}
fn string_ends_with(&self, left: &Value, right: &Value) -> bool {
match (left, right) {
(Value::String(a), Value::String(b)) => a.ends_with(b.as_str()),
_ => false,
}
}
fn estimate_selectivity(expr: &PredicateExpr) -> f64 {
match expr {
PredicateExpr::Literal(Value::Bool(true)) => 1.0,
PredicateExpr::Literal(Value::Bool(false)) => 0.0,
PredicateExpr::Literal(_) => 0.5,
PredicateExpr::Field(_) => 0.5,
PredicateExpr::Compare { op, .. } => {
match op {
CompareOp::Eq => 0.1, CompareOp::Ne => 0.9, CompareOp::Lt | CompareOp::Le | CompareOp::Gt | CompareOp::Ge => 0.3,
CompareOp::Contains | CompareOp::StartsWith | CompareOp::EndsWith => 0.2,
}
}
PredicateExpr::Logical { op, operands } => {
match op {
LogicalOp::And => operands.iter().map(Self::estimate_selectivity).product(),
LogicalOp::Or => {
let sels: Vec<f64> =
operands.iter().map(Self::estimate_selectivity).collect();
sels.iter().fold(0.0, |acc, &s| acc + s - acc * s)
}
LogicalOp::Not => 1.0 - Self::estimate_selectivity(&operands[0]),
}
}
PredicateExpr::In { list, .. } => (list.len() as f64 * 0.1).min(0.9),
PredicateExpr::Between { .. } => 0.2,
PredicateExpr::IsNull(_) => 0.05,
}
}
}
pub trait FilterPlugin: Send + Sync {
fn name(&self) -> &str;
fn evaluate(&self, doc: &dyn Filterable) -> FilterResult<bool>;
fn selectivity(&self) -> f64 {
0.5
}
fn cost(&self) -> f64 {
1.0
}
}
pub struct NativeFilter<F>
where
F: Fn(&dyn Filterable) -> bool + Send + Sync,
{
name: String,
filter_fn: F,
selectivity: f64,
}
impl<F> NativeFilter<F>
where
F: Fn(&dyn Filterable) -> bool + Send + Sync,
{
pub fn new(name: impl Into<String>, filter_fn: F) -> Self {
Self {
name: name.into(),
filter_fn,
selectivity: 0.5,
}
}
pub fn with_selectivity(mut self, selectivity: f64) -> Self {
self.selectivity = selectivity;
self
}
}
impl<F> FilterPlugin for NativeFilter<F>
where
F: Fn(&dyn Filterable) -> bool + Send + Sync,
{
fn name(&self) -> &str {
&self.name
}
fn evaluate(&self, doc: &dyn Filterable) -> FilterResult<bool> {
Ok((self.filter_fn)(doc))
}
fn selectivity(&self) -> f64 {
self.selectivity
}
fn cost(&self) -> f64 {
0.5 }
}
pub struct FilterPipeline {
filters: Vec<Arc<dyn FilterPlugin>>,
}
impl FilterPipeline {
pub fn new() -> Self {
Self {
filters: Vec::new(),
}
}
pub fn add(&mut self, filter: Arc<dyn FilterPlugin>) {
self.filters.push(filter);
self.reorder();
}
fn reorder(&mut self) {
self.filters.sort_by(|a, b| {
let score_a = (1.0 - a.selectivity()) / a.cost();
let score_b = (1.0 - b.selectivity()) / b.cost();
score_b
.partial_cmp(&score_a)
.unwrap_or(std::cmp::Ordering::Equal)
});
}
pub fn evaluate(&self, doc: &dyn Filterable) -> FilterResult<bool> {
for filter in &self.filters {
if !filter.evaluate(doc)? {
return Ok(false);
}
}
Ok(true)
}
pub fn filter_batch<'a, D: Filterable>(&self, docs: &'a [D]) -> Vec<&'a D> {
docs.iter()
.filter(|doc| self.evaluate(*doc).unwrap_or(false))
.collect()
}
pub fn len(&self) -> usize {
self.filters.len()
}
pub fn is_empty(&self) -> bool {
self.filters.is_empty()
}
pub fn combined_selectivity(&self) -> f64 {
self.filters.iter().map(|f| f.selectivity()).product()
}
}
impl Default for FilterPipeline {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct Projection {
pub include: Vec<String>,
pub exclude: Vec<String>,
}
impl Projection {
pub fn include(fields: Vec<String>) -> Self {
Self {
include: fields,
exclude: Vec::new(),
}
}
pub fn exclude(fields: Vec<String>) -> Self {
Self {
include: Vec::new(),
exclude: fields,
}
}
pub fn includes(&self, field: &str) -> bool {
if !self.include.is_empty() {
self.include.iter().any(|f| f == field)
} else if !self.exclude.is_empty() {
!self.exclude.iter().any(|f| f == field)
} else {
true }
}
pub fn apply(&self, doc: &Document) -> Document {
let mut result = Document::new(doc.id);
for (name, value) in &doc.fields {
if self.includes(name) {
result.set_field(name.clone(), value.clone());
}
}
result
}
}
#[derive(Debug, Clone)]
pub struct WasmPluginConfig {
pub max_memory: usize,
pub max_fuel: u64,
pub timeout_ms: u64,
pub allow_network: bool,
pub allow_filesystem: bool,
}
impl Default for WasmPluginConfig {
fn default() -> Self {
Self {
max_memory: 16 * 1024 * 1024, max_fuel: 100_000,
timeout_ms: 10,
allow_network: false,
allow_filesystem: false,
}
}
}
#[derive(Debug, Clone)]
pub struct WasmModule {
pub name: String,
pub bytecode: Vec<u8>,
pub config: WasmPluginConfig,
}
impl WasmModule {
pub fn new(name: impl Into<String>, bytecode: Vec<u8>) -> Self {
Self {
name: name.into(),
bytecode,
config: WasmPluginConfig::default(),
}
}
pub fn with_config(mut self, config: WasmPluginConfig) -> Self {
self.config = config;
self
}
pub fn validate(&self) -> FilterResult<()> {
if self.bytecode.len() < 8 {
return Err(FilterError::PluginLoadError(
"WASM bytecode too short".to_string(),
));
}
if &self.bytecode[0..4] != b"\0asm" {
return Err(FilterError::PluginLoadError(
"Invalid WASM magic number".to_string(),
));
}
Ok(())
}
pub fn size(&self) -> usize {
self.bytecode.len()
}
}
pub struct WasmFilter {
module: WasmModule,
stats: WasmFilterStats,
}
#[derive(Debug, Clone, Default)]
pub struct WasmFilterStats {
pub evaluations: u64,
pub fuel_consumed: u64,
pub time_us: u64,
pub errors: u64,
}
impl WasmFilter {
pub fn new(module: WasmModule) -> FilterResult<Self> {
module.validate()?;
Ok(Self {
module,
stats: WasmFilterStats::default(),
})
}
pub fn name(&self) -> &str {
&self.module.name
}
pub fn config(&self) -> &WasmPluginConfig {
&self.module.config
}
pub fn stats(&self) -> &WasmFilterStats {
&self.stats
}
pub fn evaluate(&mut self, doc: &dyn Filterable) -> FilterResult<bool> {
self.stats.evaluations += 1;
let _ = doc.id();
self.stats.fuel_consumed += 100;
Ok(true)
}
}
impl FilterPlugin for WasmFilter {
fn name(&self) -> &str {
&self.module.name
}
fn evaluate(&self, doc: &dyn Filterable) -> FilterResult<bool> {
let _ = doc.id();
Ok(true)
}
fn selectivity(&self) -> f64 {
0.5 }
fn cost(&self) -> f64 {
5.0 }
}
pub struct WasmPluginRegistry {
plugins: HashMap<String, Arc<WasmFilter>>,
max_plugins: usize,
}
impl WasmPluginRegistry {
pub fn new() -> Self {
Self {
plugins: HashMap::new(),
max_plugins: 100,
}
}
pub fn with_max_plugins(max_plugins: usize) -> Self {
Self {
plugins: HashMap::new(),
max_plugins,
}
}
pub fn register(&mut self, module: WasmModule) -> FilterResult<()> {
if self.plugins.len() >= self.max_plugins {
return Err(FilterError::PluginLoadError(
"plugin registry full".to_string(),
));
}
let name = module.name.clone();
let filter = WasmFilter::new(module)?;
self.plugins.insert(name, Arc::new(filter));
Ok(())
}
pub fn get(&self, name: &str) -> Option<Arc<WasmFilter>> {
self.plugins.get(name).cloned()
}
pub fn remove(&mut self, name: &str) -> bool {
self.plugins.remove(name).is_some()
}
pub fn list(&self) -> Vec<&str> {
self.plugins.keys().map(|s| s.as_str()).collect()
}
pub fn len(&self) -> usize {
self.plugins.len()
}
pub fn is_empty(&self) -> bool {
self.plugins.is_empty()
}
}
impl Default for WasmPluginRegistry {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn sample_doc() -> Document {
let mut doc = Document::new(1);
doc.set_field("name", Value::String("Alice".to_string()));
doc.set_field("age", Value::Int(30));
doc.set_field("score", Value::Float(95.5));
doc.set_field("active", Value::Bool(true));
doc
}
#[test]
fn test_value_truthy() {
assert!(!Value::Null.is_truthy());
assert!(!Value::Bool(false).is_truthy());
assert!(Value::Bool(true).is_truthy());
assert!(!Value::Int(0).is_truthy());
assert!(Value::Int(1).is_truthy());
assert!(!Value::String("".to_string()).is_truthy());
assert!(Value::String("hello".to_string()).is_truthy());
}
#[test]
fn test_simple_equality() {
let expr = PredicateExpr::eq(
PredicateExpr::field("age"),
PredicateExpr::lit(Value::Int(30)),
);
let eval = PredicateEvaluator::new(expr);
let doc = sample_doc();
assert!(eval.evaluate(&doc).unwrap());
}
#[test]
fn test_comparison() {
let expr = PredicateExpr::Compare {
op: CompareOp::Gt,
left: Box::new(PredicateExpr::field("score")),
right: Box::new(PredicateExpr::lit(Value::Float(90.0))),
};
let eval = PredicateEvaluator::new(expr);
let doc = sample_doc();
assert!(eval.evaluate(&doc).unwrap());
}
#[test]
fn test_logical_and() {
let expr = PredicateExpr::and(vec![
PredicateExpr::eq(
PredicateExpr::field("active"),
PredicateExpr::lit(Value::Bool(true)),
),
PredicateExpr::Compare {
op: CompareOp::Ge,
left: Box::new(PredicateExpr::field("age")),
right: Box::new(PredicateExpr::lit(Value::Int(18))),
},
]);
let eval = PredicateEvaluator::new(expr);
let doc = sample_doc();
assert!(eval.evaluate(&doc).unwrap());
}
#[test]
fn test_logical_or() {
let expr = PredicateExpr::or(vec![
PredicateExpr::eq(
PredicateExpr::field("name"),
PredicateExpr::lit(Value::String("Bob".to_string())),
),
PredicateExpr::eq(
PredicateExpr::field("name"),
PredicateExpr::lit(Value::String("Alice".to_string())),
),
]);
let eval = PredicateEvaluator::new(expr);
let doc = sample_doc();
assert!(eval.evaluate(&doc).unwrap());
}
#[test]
fn test_string_contains() {
let expr = PredicateExpr::Compare {
op: CompareOp::Contains,
left: Box::new(PredicateExpr::field("name")),
right: Box::new(PredicateExpr::lit(Value::String("lic".to_string()))),
};
let eval = PredicateEvaluator::new(expr);
let doc = sample_doc();
assert!(eval.evaluate(&doc).unwrap());
}
#[test]
fn test_between() {
let expr = PredicateExpr::Between {
value: Box::new(PredicateExpr::field("age")),
low: Box::new(PredicateExpr::lit(Value::Int(25))),
high: Box::new(PredicateExpr::lit(Value::Int(35))),
};
let eval = PredicateEvaluator::new(expr);
let doc = sample_doc();
assert!(eval.evaluate(&doc).unwrap());
}
#[test]
fn test_native_filter() {
let filter = NativeFilter::new("age_filter", |doc| {
doc.get_field("age")
.and_then(|v| v.as_int())
.map(|age| age >= 18)
.unwrap_or(false)
});
let doc = sample_doc();
assert!(filter.evaluate(&doc).unwrap());
}
#[test]
fn test_filter_pipeline() {
let mut pipeline = FilterPipeline::new();
let filter1 = Arc::new(
NativeFilter::new("active_check", |doc| {
doc.get_field("active")
.map(|v| v.is_truthy())
.unwrap_or(false)
})
.with_selectivity(0.8),
);
let filter2 = Arc::new(
NativeFilter::new("score_check", |doc| {
doc.get_field("score")
.and_then(|v| v.as_float())
.map(|s| s > 50.0)
.unwrap_or(false)
})
.with_selectivity(0.3),
);
pipeline.add(filter1);
pipeline.add(filter2);
let doc = sample_doc();
assert!(pipeline.evaluate(&doc).unwrap());
}
#[test]
fn test_selectivity_estimation() {
let expr = PredicateExpr::and(vec![
PredicateExpr::eq(PredicateExpr::field("x"), PredicateExpr::lit(Value::Int(1))),
PredicateExpr::eq(PredicateExpr::field("y"), PredicateExpr::lit(Value::Int(2))),
]);
let eval = PredicateEvaluator::new(expr);
assert!((eval.selectivity() - 0.01).abs() < 0.001);
}
#[test]
fn test_projection_include() {
let proj = Projection::include(vec!["name".to_string(), "age".to_string()]);
assert!(proj.includes("name"));
assert!(proj.includes("age"));
assert!(!proj.includes("score"));
}
#[test]
fn test_projection_apply() {
let proj = Projection::include(vec!["name".to_string()]);
let doc = sample_doc();
let result = proj.apply(&doc);
assert!(result.get_field("name").is_some());
assert!(result.get_field("age").is_none());
}
#[test]
fn test_filter_batch() {
let mut pipeline = FilterPipeline::new();
pipeline.add(Arc::new(NativeFilter::new("age_filter", |doc| {
doc.get_field("age")
.and_then(|v| v.as_int())
.map(|age| age >= 25)
.unwrap_or(false)
})));
let mut docs = Vec::new();
for i in 0..10 {
let mut doc = Document::new(i);
doc.set_field("age", Value::Int((i * 5) as i64));
docs.push(doc);
}
let filtered = pipeline.filter_batch(&docs);
assert_eq!(filtered.len(), 5);
}
#[test]
fn test_wasm_plugin_config_default() {
let config = WasmPluginConfig::default();
assert_eq!(config.max_memory, 16 * 1024 * 1024);
assert_eq!(config.max_fuel, 100_000);
assert_eq!(config.timeout_ms, 10);
assert!(!config.allow_network);
assert!(!config.allow_filesystem);
}
#[test]
fn test_wasm_module_validation() {
let valid_wasm = vec![0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00];
let module = WasmModule::new("test", valid_wasm);
assert!(module.validate().is_ok());
let short = vec![0x00, 0x61];
let module = WasmModule::new("short", short);
assert!(module.validate().is_err());
let invalid = vec![0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
let module = WasmModule::new("invalid", invalid);
assert!(module.validate().is_err());
}
#[test]
fn test_wasm_filter_creation() {
let wasm = vec![0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00];
let module = WasmModule::new("test_filter", wasm);
let filter = WasmFilter::new(module).unwrap();
assert_eq!(filter.name(), "test_filter");
assert_eq!(filter.stats().evaluations, 0);
}
#[test]
fn test_wasm_registry() {
let mut registry = WasmPluginRegistry::new();
assert!(registry.is_empty());
let wasm = vec![0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00];
registry
.register(WasmModule::new("filter1", wasm.clone()))
.unwrap();
registry
.register(WasmModule::new("filter2", wasm.clone()))
.unwrap();
assert_eq!(registry.len(), 2);
assert!(registry.get("filter1").is_some());
assert!(registry.get("nonexistent").is_none());
let names = registry.list();
assert!(names.contains(&"filter1"));
assert!(names.contains(&"filter2"));
assert!(registry.remove("filter1"));
assert_eq!(registry.len(), 1);
}
#[test]
fn test_wasm_registry_limit() {
let mut registry = WasmPluginRegistry::with_max_plugins(2);
let wasm = vec![0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00];
registry
.register(WasmModule::new("p1", wasm.clone()))
.unwrap();
registry
.register(WasmModule::new("p2", wasm.clone()))
.unwrap();
let result = registry.register(WasmModule::new("p3", wasm));
assert!(result.is_err());
}
#[test]
fn test_wasm_filter_as_plugin() {
let wasm = vec![0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00];
let module = WasmModule::new("plugin", wasm);
let filter = WasmFilter::new(module).unwrap();
assert_eq!(filter.name(), "plugin");
assert!(filter.cost() > 1.0);
let doc = sample_doc();
assert!(filter.evaluate(&doc).is_ok());
}
}