sealmap_model/
codebase.rs1use std::collections::{BTreeMap, BTreeSet};
2
3use serde::{Deserialize, Serialize};
4
5use crate::path::SourcePath;
6use crate::source::SourceFile;
7use crate::sym::SymbolId;
8use crate::symbol::{Relation, RelationKind, Symbol, SymbolKind};
9
10#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
16pub struct Codebase {
17 pub schema_version: u32,
19 pub name: String,
21 pub files: BTreeMap<SourcePath, SourceFile>,
23 pub symbols: BTreeMap<SymbolId, Symbol>,
25 pub relations: BTreeSet<Relation>,
27}
28
29impl Codebase {
30 pub fn new(name: impl Into<String>) -> Self {
32 Self { schema_version: crate::MODEL_SCHEMA_VERSION, name: name.into(), ..Self::default() }
33 }
34
35 pub fn from_json(text: &str) -> Result<Self, ModelJsonError> {
48 #[derive(Deserialize)]
49 struct Probe {
50 schema_version: Option<u32>,
51 schema: Option<u32>,
52 }
53 let probe: Probe = serde_json::from_str(text).map_err(ModelJsonError::Json)?;
54 let found = probe.schema_version.or(probe.schema);
55 if found != Some(crate::MODEL_SCHEMA_VERSION) {
56 return Err(ModelJsonError::Version { found, expected: crate::MODEL_SCHEMA_VERSION });
57 }
58 serde_json::from_str(text).map_err(ModelJsonError::Json)
59 }
60
61 pub fn add_file(&mut self, file: SourceFile) {
63 self.files.insert(file.path.clone(), file);
64 }
65
66 pub fn add_symbol(&mut self, symbol: Symbol) {
72 match self.symbols.get_mut(&symbol.id) {
73 Some(existing) => {
74 existing.sig_hash = existing.sig_hash.merge(symbol.sig_hash);
75 existing.body_hash = existing.body_hash.merge(symbol.body_hash);
76 existing.members.extend(symbol.members);
77 for tag in symbol.tags {
78 if !existing.tags.contains(&tag) {
79 existing.tags.push(tag);
80 }
81 }
82 }
83 None => {
84 self.symbols.insert(symbol.id.clone(), symbol);
85 }
86 }
87 }
88
89 pub fn add_relation(&mut self, relation: Relation) {
91 if relation.from != relation.to {
92 self.relations.insert(relation);
93 }
94 }
95
96 pub fn file(&self, path: &SourcePath) -> Option<&SourceFile> {
98 self.files.get(path)
99 }
100
101 pub fn symbol(&self, id: &SymbolId) -> Option<&Symbol> {
103 self.symbols.get(id)
104 }
105
106 pub fn is_internal(&self, id: &SymbolId) -> bool {
108 self.symbols.contains_key(id)
109 }
110
111 pub fn symbols_in_file<'a>(&'a self, path: &'a SourcePath) -> impl Iterator<Item = &'a Symbol> {
113 self.symbols.values().filter(move |s| &s.file == path)
114 }
115
116 pub fn symbols_of_kind(&self, kind: SymbolKind) -> impl Iterator<Item = &Symbol> {
118 self.symbols.values().filter(move |s| s.kind == kind)
119 }
120
121 pub fn children<'a>(&'a self, id: &'a SymbolId) -> impl Iterator<Item = &'a Symbol> {
123 self.symbols.values().filter(move |s| s.parent.as_ref() == Some(id))
124 }
125
126 pub fn relations_from<'a>(&'a self, id: &'a SymbolId) -> impl Iterator<Item = &'a Relation> {
131 let lower = Relation::new(id.clone(), SymbolId::min_value(), RelationKind::Contains, crate::Confidence::Exact);
132 self.relations.range(lower..).take_while(move |r| &r.from == id)
133 }
134
135 pub fn relations_to<'a>(&'a self, id: &'a SymbolId) -> impl Iterator<Item = &'a Relation> {
137 self.relations.iter().filter(move |r| &r.to == id)
138 }
139
140 pub fn relations_of_kind(&self, kind: RelationKind) -> impl Iterator<Item = &Relation> {
142 self.relations.iter().filter(move |r| r.kind == kind)
143 }
144
145 pub fn owner_of(&self, id: &SymbolId) -> Option<SymbolId> {
151 match self.symbols.get(id) {
152 Some(sym) if sym.kind == SymbolKind::Method || sym.kind == SymbolKind::Function => sym.parent.clone(),
153 Some(sym) => Some(sym.id.clone()),
154 None => id.parent(),
155 }
156 }
157
158 pub fn stats(&self) -> CodebaseStats {
160 let mut stats = CodebaseStats {
161 files: self.files.len(),
162 symbols: self.symbols.len(),
163 relations: self.relations.len(),
164 ..CodebaseStats::default()
165 };
166 for sym in self.symbols.values() {
167 if let Some(flow) = &sym.flow {
168 stats.flows += 1;
169 stats.calls += flow.call_count();
170 }
171 }
172 stats
173 }
174}
175
176#[derive(Debug, thiserror::Error)]
178pub enum ModelJsonError {
179 #[error("invalid model JSON: {0}")]
181 Json(serde_json::Error),
182 #[error("model schema version {found:?} is not supported (expected {expected})")]
184 Version {
185 found: Option<u32>,
187 expected: u32,
189 },
190}
191
192#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
194pub struct CodebaseStats {
195 pub files: usize,
197 pub symbols: usize,
199 pub relations: usize,
201 pub flows: usize,
203 pub calls: usize,
205}