use crate::symbol::{FileSpan, SymbolId, SymbolPath, Uuid, Visibility};
use crate::SymbolKind;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, Default)]
pub struct DiscoveryResult {
pub symbols: Vec<DiscoveredSymbol>,
pub relations: Option<RelationGraph>,
pub total_matches: usize,
pub truncated: bool,
}
impl DiscoveryResult {
pub fn new() -> Self {
Self::default()
}
pub fn add(&mut self, symbol: DiscoveredSymbol) {
self.symbols.push(symbol);
}
pub fn len(&self) -> usize {
self.symbols.len()
}
pub fn is_empty(&self) -> bool {
self.symbols.is_empty()
}
pub fn first(&self) -> Option<&DiscoveredSymbol> {
self.symbols.first()
}
pub fn iter(&self) -> impl Iterator<Item = &DiscoveredSymbol> {
self.symbols.iter()
}
pub fn paths(&self) -> impl Iterator<Item = &SymbolPath> {
self.symbols.iter().map(|s| &s.path)
}
pub fn ids(&self) -> impl Iterator<Item = SymbolId> + '_ {
self.symbols.iter().map(|s| s.id)
}
}
#[derive(Debug, Clone, Serialize)]
pub struct DiscoveredSymbol {
pub id: SymbolId,
#[serde(skip_serializing_if = "Option::is_none")]
pub uuid: Option<Uuid>,
pub path: SymbolPath,
pub kind: SymbolKind,
pub span: Option<FileSpan>,
pub visibility: Option<Visibility>,
pub score: f32,
pub ref_count: usize,
pub impl_count: usize,
}
impl DiscoveredSymbol {
pub fn new(id: SymbolId, path: SymbolPath, kind: SymbolKind) -> Self {
Self {
id,
uuid: None,
path,
kind,
span: None,
visibility: None,
score: 1.0,
ref_count: 0,
impl_count: 0,
}
}
pub fn with_uuid(mut self, uuid: Uuid) -> Self {
self.uuid = Some(uuid);
self
}
pub fn with_span(mut self, span: FileSpan) -> Self {
self.span = Some(span);
self
}
pub fn with_visibility(mut self, visibility: Visibility) -> Self {
self.visibility = Some(visibility);
self
}
pub fn with_score(mut self, score: f32) -> Self {
self.score = score;
self
}
pub fn with_ref_count(mut self, ref_count: usize) -> Self {
self.ref_count = ref_count;
self
}
pub fn with_impl_count(mut self, impl_count: usize) -> Self {
self.impl_count = impl_count;
self
}
pub fn is_public(&self) -> bool {
self.visibility.as_ref().is_some_and(|v| v.is_public())
}
}
#[derive(Debug, Clone, Default)]
pub struct RelationGraph {
relations: HashMap<SymbolId, Vec<Relation>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Relation {
pub target: SymbolId,
pub kind: RelationKind,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum RelationKind {
Calls,
CalledBy,
TypeReferences,
TypeReferencedBy,
Implements,
ImplementedBy,
Contains,
ContainedBy,
}
impl RelationGraph {
pub fn new() -> Self {
Self::default()
}
pub fn add(&mut self, source: SymbolId, target: SymbolId, kind: RelationKind) {
self.relations
.entry(source)
.or_default()
.push(Relation { target, kind });
}
pub fn get(&self, id: SymbolId) -> &[Relation] {
self.relations.get(&id).map_or(&[], |v| v.as_slice())
}
pub fn get_by_kind(&self, id: SymbolId, kind: RelationKind) -> Vec<SymbolId> {
self.get(id)
.iter()
.filter(|r| r.kind == kind)
.map(|r| r.target)
.collect()
}
pub fn is_empty(&self) -> bool {
self.relations.is_empty()
}
pub fn len(&self) -> usize {
self.relations.len()
}
pub fn iter(&self) -> impl Iterator<Item = (SymbolId, &[Relation])> {
self.relations
.iter()
.map(|(id, rels)| (*id, rels.as_slice()))
}
pub fn sources(&self) -> impl Iterator<Item = SymbolId> + '_ {
self.relations.keys().copied()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_discovery_result() {
let result = DiscoveryResult::new();
assert!(result.is_empty());
assert_eq!(result.len(), 0);
}
#[test]
fn test_relation_graph() {
use slotmap::SlotMap;
let mut slots: SlotMap<SymbolId, ()> = SlotMap::with_key();
let id1 = slots.insert(());
let id2 = slots.insert(());
let mut graph = RelationGraph::new();
graph.add(id1, id2, RelationKind::Calls);
assert!(!graph.is_empty());
assert_eq!(graph.get(id1).len(), 1);
assert_eq!(graph.get_by_kind(id1, RelationKind::Calls), vec![id2]);
}
#[test]
fn test_relation_kind() {
assert_ne!(RelationKind::Calls, RelationKind::CalledBy);
}
}