use async_trait::async_trait;
use crate::error::Result;
#[async_trait]
pub trait MemorySearcher: Send + Sync {
fn name(&self) -> &str;
fn score(&self, chunk: &str, query: &str) -> f32;
async fn score_batch(&self, chunks: &[&str], query: &str) -> Vec<f32> {
chunks.iter().map(|c| self.score(c, query)).collect()
}
async fn index(&self, _key: &str, _text: &str) -> Result<()> {
Ok(())
}
async fn remove(&self, _key: &str) -> Result<()> {
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
struct FixedScorer(f32);
#[async_trait]
impl MemorySearcher for FixedScorer {
fn name(&self) -> &str {
"fixed"
}
fn score(&self, _chunk: &str, _query: &str) -> f32 {
self.0
}
}
#[test]
fn test_trait_object_construction() {
let searcher: Box<dyn MemorySearcher> = Box::new(FixedScorer(0.5));
assert_eq!(searcher.name(), "fixed");
assert_eq!(searcher.score("hello", "world"), 0.5);
}
#[tokio::test]
async fn test_default_score_batch() {
let searcher = FixedScorer(0.7);
let chunks = vec!["a", "b", "c"];
let scores = searcher.score_batch(&chunks, "query").await;
assert_eq!(scores, vec![0.7, 0.7, 0.7]);
}
#[tokio::test]
async fn test_default_index_and_remove_are_noop() {
let searcher = FixedScorer(0.0);
assert!(searcher.index("key", "text").await.is_ok());
assert!(searcher.remove("key").await.is_ok());
}
}