projectatlas_core/
symbols.rs

1//! Purpose: Define `ProjectAtlas` symbol graph domain types.
2
3use serde::{Deserialize, Serialize};
4use std::fmt;
5
6/// Kind of symbol stored in the `ProjectAtlas` graph.
7#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
8#[serde(rename_all = "kebab-case")]
9pub enum SymbolKind {
10    /// A free function or language-level function declaration.
11    Function,
12    /// A method declaration associated with a type or class.
13    Method,
14    /// A class declaration.
15    Class,
16    /// A Rust-style struct or record declaration.
17    Struct,
18    /// An enum declaration.
19    Enum,
20    /// A trait declaration.
21    Trait,
22    /// An interface declaration.
23    Interface,
24    /// A module, namespace, package, or source unit.
25    Module,
26    /// A type alias or type declaration.
27    Type,
28    /// A constant, static, field, or variable declaration worth indexing.
29    Value,
30    /// An import, use, include, using, or package dependency edge source.
31    Import,
32    /// A package manifest entry such as a Cargo package.
33    Package,
34    /// A workspace manifest entry such as a Cargo workspace.
35    Workspace,
36    /// A dependency declared in a manifest.
37    Dependency,
38    /// A symbol that did not map cleanly to a richer kind.
39    Unknown,
40}
41
42impl fmt::Display for SymbolKind {
43    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
44        formatter.write_str(match self {
45            Self::Function => "function",
46            Self::Method => "method",
47            Self::Class => "class",
48            Self::Struct => "struct",
49            Self::Enum => "enum",
50            Self::Trait => "trait",
51            Self::Interface => "interface",
52            Self::Module => "module",
53            Self::Type => "type",
54            Self::Value => "value",
55            Self::Import => "import",
56            Self::Package => "package",
57            Self::Workspace => "workspace",
58            Self::Dependency => "dependency",
59            Self::Unknown => "unknown",
60        })
61    }
62}
63
64impl SymbolKind {
65    /// Parse a persisted symbol kind.
66    #[must_use]
67    pub fn from_db(value: &str) -> Self {
68        match value {
69            "function" => Self::Function,
70            "method" => Self::Method,
71            "class" => Self::Class,
72            "struct" => Self::Struct,
73            "enum" => Self::Enum,
74            "trait" => Self::Trait,
75            "interface" => Self::Interface,
76            "module" => Self::Module,
77            "type" => Self::Type,
78            "value" => Self::Value,
79            "import" => Self::Import,
80            "package" => Self::Package,
81            "workspace" => Self::Workspace,
82            "dependency" => Self::Dependency,
83            _ => Self::Unknown,
84        }
85    }
86}
87
88/// Kind of graph relation stored for symbols.
89#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
90#[serde(rename_all = "kebab-case")]
91pub enum RelationKind {
92    /// One symbol contains another symbol.
93    Contains,
94    /// A source imports or includes another module.
95    Imports,
96    /// A source symbol calls a target symbol or expression.
97    Calls,
98    /// A package or manifest depends on another package.
99    DependsOn,
100}
101
102impl fmt::Display for RelationKind {
103    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
104        formatter.write_str(match self {
105            Self::Contains => "contains",
106            Self::Imports => "imports",
107            Self::Calls => "calls",
108            Self::DependsOn => "depends-on",
109        })
110    }
111}
112
113impl RelationKind {
114    /// Parse a persisted relation kind.
115    #[must_use]
116    pub fn from_db(value: &str) -> Option<Self> {
117        match value {
118            "contains" => Some(Self::Contains),
119            "imports" => Some(Self::Imports),
120            "calls" => Some(Self::Calls),
121            "depends-on" => Some(Self::DependsOn),
122            _ => None,
123        }
124    }
125}
126
127/// Parser strategy used to produce a graph entry.
128#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
129#[serde(rename_all = "kebab-case")]
130pub enum ParserKind {
131    /// A tree-sitter grammar produced the result.
132    TreeSitter,
133    /// A manifest parser produced the result.
134    Manifest,
135    /// A deterministic structural adapter produced the result.
136    Structural,
137    /// A conservative regex fallback produced the result.
138    Fallback,
139}
140
141impl fmt::Display for ParserKind {
142    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
143        formatter.write_str(match self {
144            Self::TreeSitter => "tree-sitter",
145            Self::Manifest => "manifest",
146            Self::Structural => "structural",
147            Self::Fallback => "fallback",
148        })
149    }
150}
151
152impl ParserKind {
153    /// Parse a persisted parser kind.
154    #[must_use]
155    pub fn from_db(value: &str) -> Self {
156        match value {
157            "tree-sitter" => Self::TreeSitter,
158            "manifest" => Self::Manifest,
159            "structural" => Self::Structural,
160            _ => Self::Fallback,
161        }
162    }
163}
164
165/// A code or manifest symbol indexed by `ProjectAtlas`.
166#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
167pub struct CodeSymbol {
168    /// Repository-relative file path.
169    pub path: String,
170    /// Detected language or file family.
171    pub language: Option<String>,
172    /// Symbol name.
173    pub name: String,
174    /// Symbol kind.
175    pub kind: SymbolKind,
176    /// Compact declaration signature or source row.
177    pub signature: String,
178    /// Whether the declaration is exported or publicly visible.
179    pub exported: bool,
180    /// Extracted doc comment or docstring associated with the symbol.
181    pub documentation: Option<String>,
182    /// One-based start line.
183    pub line_start: usize,
184    /// One-based end line.
185    pub line_end: usize,
186    /// Optional containing symbol name.
187    pub parent: Option<String>,
188    /// Parser strategy that produced this symbol.
189    pub parser: ParserKind,
190    /// Optional detail, usually the original parser node kind.
191    pub detail: Option<String>,
192}
193
194/// A directed relation between symbols or source-level references.
195#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
196pub struct SymbolRelation {
197    /// Repository-relative file path.
198    pub path: String,
199    /// Source symbol name or module sentinel.
200    pub source_name: String,
201    /// Target symbol, import path, or dependency name.
202    pub target_name: String,
203    /// Relation kind.
204    pub kind: RelationKind,
205    /// One-based line where the relation appears.
206    pub line: usize,
207    /// Compact source context for the relation.
208    pub context: String,
209    /// Parser strategy that produced this relation.
210    pub parser: ParserKind,
211}
212
213/// Symbol graph extracted from one file.
214#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
215pub struct SymbolGraph {
216    /// Repository-relative file path.
217    pub path: String,
218    /// Detected language or file family.
219    pub language: Option<String>,
220    /// Primary parser strategy used for the file.
221    pub parser: ParserKind,
222    /// Extracted declaration and manifest symbols.
223    pub symbols: Vec<CodeSymbol>,
224    /// Extracted import, dependency, containment, and call relations.
225    pub relations: Vec<SymbolRelation>,
226}
227
228/// File-level parser metadata persisted even when a graph has no symbols.
229#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
230pub struct SourceParseMetadata {
231    /// Repository-relative file path.
232    pub path: String,
233    /// Detected language or file family.
234    pub language: Option<String>,
235    /// Primary parser strategy used for the file.
236    pub parser: ParserKind,
237    /// Number of declaration or manifest symbols emitted for this file.
238    pub symbol_count: usize,
239    /// Number of relations emitted for this file.
240    pub relation_count: usize,
241}
242
243impl SourceParseMetadata {
244    /// Build persisted parser metadata from a graph.
245    #[must_use]
246    pub fn from_graph(graph: &SymbolGraph) -> Self {
247        Self {
248            path: graph.path.clone(),
249            language: graph.language.clone(),
250            parser: graph.parser,
251            symbol_count: graph.symbols.len(),
252            relation_count: graph.relations.len(),
253        }
254    }
255}