use anyhow::Result;
use async_trait::async_trait;
use super::impact::{ImpactAnalyzer, ImpactConfig, ImpactResult};
use super::refs::{FileSymbols, RefAnalyzer, SymbolRefs};
use crate::index::Embedder;
use crate::storage::{MetadataStore, VectorStore};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum StructuralOp {
FindRefs,
ListSymbols,
Impact,
}
#[async_trait]
pub trait StructuralBackend: Send {
fn name(&self) -> &'static str;
fn supports(&self, op: StructuralOp) -> bool;
async fn find_refs(
&mut self,
symbol_name: &str,
symbol_type: Option<&str>,
limit: usize,
repo: Option<&str>,
) -> Result<SymbolRefs>;
async fn list_symbols(&mut self, file_path: &str, repo: Option<&str>) -> Result<FileSymbols>;
async fn impact(
&mut self,
target: &str,
config: &ImpactConfig,
depth: u32,
repo: Option<&str>,
) -> Result<Vec<ImpactResult>>;
}
pub struct IndexBackend<'a> {
vector_store: &'a mut VectorStore,
impact_deps: Option<ImpactDeps<'a>>,
}
struct ImpactDeps<'a> {
metadata_store: &'a mut MetadataStore,
embedder: &'a mut Embedder,
}
impl<'a> IndexBackend<'a> {
pub fn new(vector_store: &'a mut VectorStore) -> Self {
Self {
vector_store,
impact_deps: None,
}
}
pub fn with_impact(
vector_store: &'a mut VectorStore,
metadata_store: &'a mut MetadataStore,
embedder: &'a mut Embedder,
) -> Self {
Self {
vector_store,
impact_deps: Some(ImpactDeps {
metadata_store,
embedder,
}),
}
}
}
#[async_trait]
impl<'a> StructuralBackend for IndexBackend<'a> {
fn name(&self) -> &'static str {
"index"
}
fn supports(&self, op: StructuralOp) -> bool {
match op {
StructuralOp::FindRefs | StructuralOp::ListSymbols => true,
StructuralOp::Impact => self.impact_deps.is_some(),
}
}
async fn find_refs(
&mut self,
symbol_name: &str,
symbol_type: Option<&str>,
limit: usize,
repo: Option<&str>,
) -> Result<SymbolRefs> {
RefAnalyzer::new(self.vector_store)
.find_refs(symbol_name, symbol_type, limit, repo)
.await
}
async fn list_symbols(&mut self, file_path: &str, repo: Option<&str>) -> Result<FileSymbols> {
RefAnalyzer::new(self.vector_store)
.list_symbols(file_path, repo)
.await
}
async fn impact(
&mut self,
target: &str,
config: &ImpactConfig,
depth: u32,
repo: Option<&str>,
) -> Result<Vec<ImpactResult>> {
let Some(deps) = self.impact_deps.as_mut() else {
anyhow::bail!(
"impact is not available on the '{}' backend as constructed — it needs the \
metadata store and embedder (IndexBackend::with_impact); probe \
supports(StructuralOp::Impact) before calling",
"index"
)
};
ImpactAnalyzer::new(deps.metadata_store, self.vector_store, deps.embedder)
.analyze(target, config, depth, repo)
.await
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_seam_is_object_safe() {
fn _takes_dyn(_b: &mut dyn StructuralBackend) {}
}
}