use anyhow::Result;
use async_trait::async_trait;
pub use trusty_common::embedder::{FastEmbedder, EMBED_DIM};
#[cfg(any(test, feature = "test-support"))]
pub use trusty_common::embedder::MockEmbedder;
#[async_trait]
pub trait Embedder: Send + Sync {
async fn embed(&self, text: &str) -> Result<Vec<f32>>;
async fn embed_batch(&self, texts: &[&str]) -> Result<Vec<Vec<f32>>>;
fn dimension(&self) -> usize;
fn provider(&self) -> trusty_common::embedder::ExecutionProvider {
trusty_common::embedder::ExecutionProvider::Cpu
}
}
#[async_trait]
impl<E> Embedder for E
where
E: trusty_common::embedder::Embedder,
{
async fn embed(&self, text: &str) -> Result<Vec<f32>> {
trusty_common::embedder::embed_one(self, text).await
}
async fn embed_batch(&self, texts: &[&str]) -> Result<Vec<Vec<f32>>> {
let owned: Vec<String> = texts.iter().map(|s| (*s).to_owned()).collect();
<E as trusty_common::embedder::Embedder>::embed_batch(self, &owned).await
}
fn dimension(&self) -> usize {
<E as trusty_common::embedder::Embedder>::dimension(self)
}
fn provider(&self) -> trusty_common::embedder::ExecutionProvider {
<E as trusty_common::embedder::Embedder>::provider(self)
}
}
#[cfg(test)]
mod tests {
use super::*;
use trusty_common::embedder::ExecutionProvider;
#[tokio::test]
async fn embed_adapter_delegates_embed_batch_correctly() {
const DIM: usize = 16;
let mock = MockEmbedder::new(DIM);
let texts = ["hello world", "rust async tokio", "code search engine"];
let result = Embedder::embed_batch(&mock, &texts)
.await
.expect("embed_batch should not fail with MockEmbedder");
assert_eq!(
result.len(),
texts.len(),
"embed_batch must return one vector per input"
);
for (i, vec) in result.iter().enumerate() {
assert_eq!(
vec.len(),
DIM,
"vector[{i}] has wrong dimension: expected {DIM}, got {}",
vec.len()
);
}
for (i, vec) in result.iter().enumerate() {
let nonzero = vec.iter().any(|&x| x != 0.0);
assert!(
nonzero,
"vector[{i}] is all zeros — MockEmbedder contract violated"
);
}
}
#[tokio::test]
async fn embed_adapter_produces_distinct_vectors_for_distinct_inputs() {
const DIM: usize = 32;
let mock = MockEmbedder::new(DIM);
let texts = ["alpha", "beta"];
let result = Embedder::embed_batch(&mock, &texts)
.await
.expect("embed_batch should not fail");
assert_ne!(
result[0], result[1],
"distinct inputs must produce distinct embedding vectors"
);
}
#[test]
fn embed_adapter_provider_passthrough_returns_cpu_for_mock() {
let mock = MockEmbedder::new(8);
assert_eq!(
Embedder::provider(&mock),
ExecutionProvider::Cpu,
"provider() passthrough must return Cpu for MockEmbedder"
);
}
}