1use serde::{Deserialize, Serialize};
4use std::fmt;
5
6pub const MODULE_RELATION_SOURCE: &str = "<module>";
8
9#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
11#[serde(rename_all = "kebab-case")]
12pub enum SymbolKind {
13 Function,
15 Method,
17 Class,
19 Struct,
21 Enum,
23 Trait,
25 Interface,
27 Module,
29 Heading,
31 Type,
33 Value,
35 Import,
37 Package,
39 Workspace,
41 Dependency,
43 Unknown,
45}
46
47impl fmt::Display for SymbolKind {
48 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
49 formatter.write_str(match self {
50 Self::Function => "function",
51 Self::Method => "method",
52 Self::Class => "class",
53 Self::Struct => "struct",
54 Self::Enum => "enum",
55 Self::Trait => "trait",
56 Self::Interface => "interface",
57 Self::Module => "module",
58 Self::Heading => "heading",
59 Self::Type => "type",
60 Self::Value => "value",
61 Self::Import => "import",
62 Self::Package => "package",
63 Self::Workspace => "workspace",
64 Self::Dependency => "dependency",
65 Self::Unknown => "unknown",
66 })
67 }
68}
69
70impl SymbolKind {
71 #[must_use]
73 pub fn from_db(value: &str) -> Self {
74 match value {
75 "function" => Self::Function,
76 "method" => Self::Method,
77 "class" => Self::Class,
78 "struct" => Self::Struct,
79 "enum" => Self::Enum,
80 "trait" => Self::Trait,
81 "interface" => Self::Interface,
82 "module" => Self::Module,
83 "heading" => Self::Heading,
84 "type" => Self::Type,
85 "value" => Self::Value,
86 "import" => Self::Import,
87 "package" => Self::Package,
88 "workspace" => Self::Workspace,
89 "dependency" => Self::Dependency,
90 _ => Self::Unknown,
91 }
92 }
93}
94
95#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
97#[serde(rename_all = "kebab-case")]
98pub enum RelationKind {
99 Contains,
101 Imports,
103 Calls,
105 DependsOn,
107}
108
109impl fmt::Display for RelationKind {
110 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
111 formatter.write_str(match self {
112 Self::Contains => "contains",
113 Self::Imports => "imports",
114 Self::Calls => "calls",
115 Self::DependsOn => "depends-on",
116 })
117 }
118}
119
120impl RelationKind {
121 #[must_use]
123 pub fn from_db(value: &str) -> Option<Self> {
124 match value {
125 "contains" => Some(Self::Contains),
126 "imports" => Some(Self::Imports),
127 "calls" => Some(Self::Calls),
128 "depends-on" => Some(Self::DependsOn),
129 _ => None,
130 }
131 }
132}
133
134#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
136#[serde(rename_all = "kebab-case")]
137pub enum ParserKind {
138 TreeSitter,
140 Manifest,
142 Structural,
144 Fallback,
146}
147
148impl fmt::Display for ParserKind {
149 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
150 formatter.write_str(match self {
151 Self::TreeSitter => "tree-sitter",
152 Self::Manifest => "manifest",
153 Self::Structural => "structural",
154 Self::Fallback => "fallback",
155 })
156 }
157}
158
159impl ParserKind {
160 #[must_use]
162 pub fn from_db(value: &str) -> Self {
163 match value {
164 "tree-sitter" => Self::TreeSitter,
165 "manifest" => Self::Manifest,
166 "structural" => Self::Structural,
167 _ => Self::Fallback,
168 }
169 }
170}
171
172#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
174pub struct SymbolSourceSelector {
175 pub byte_start: usize,
177 pub byte_end: usize,
179 pub column_start: usize,
181 pub column_end: usize,
183}
184
185#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
187pub struct CodeSymbol {
188 pub path: String,
190 pub language: Option<String>,
192 pub name: String,
194 pub kind: SymbolKind,
196 pub signature: String,
198 pub exported: bool,
200 pub documentation: Option<String>,
202 pub line_start: usize,
204 pub line_end: usize,
206 #[serde(default, skip_serializing_if = "Option::is_none")]
208 pub source_selector: Option<SymbolSourceSelector>,
209 pub parent: Option<String>,
211 pub parser: ParserKind,
213 pub detail: Option<String>,
215}
216
217#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
219pub struct SymbolRelation {
220 pub path: String,
222 pub source_name: String,
224 pub target_name: String,
226 pub kind: RelationKind,
228 pub line: usize,
230 pub context: String,
232 pub parser: ParserKind,
234}
235
236#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
238pub struct SymbolGraph {
239 pub path: String,
241 pub language: Option<String>,
243 pub parser: ParserKind,
245 pub symbols: Vec<CodeSymbol>,
247 pub relations: Vec<SymbolRelation>,
249}
250
251#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
253pub struct SourceParseMetadata {
254 pub path: String,
256 pub language: Option<String>,
258 pub parser: ParserKind,
260 pub symbol_count: usize,
262 pub relation_count: usize,
264}
265
266impl SourceParseMetadata {
267 #[must_use]
269 pub fn from_graph(graph: &SymbolGraph) -> Self {
270 Self {
271 path: graph.path.clone(),
272 language: graph.language.clone(),
273 parser: graph.parser,
274 symbol_count: graph.symbols.len(),
275 relation_count: graph.relations.len(),
276 }
277 }
278}
279
280#[cfg(test)]
281mod tests {
282 use super::*;
283
284 #[test]
285 fn heading_symbol_kind_round_trips_without_changing_unknown_fallback() {
286 assert_eq!(SymbolKind::Heading.to_string(), "heading");
287 assert_eq!(SymbolKind::from_db("heading"), SymbolKind::Heading);
288 assert!(matches!(
289 serde_json::to_string(&SymbolKind::Heading).as_deref(),
290 Ok("\"heading\"")
291 ));
292 assert!(matches!(
293 serde_json::from_str::<SymbolKind>("\"heading\""),
294 Ok(SymbolKind::Heading)
295 ));
296 assert_eq!(SymbolKind::from_db("future-kind"), SymbolKind::Unknown);
297 }
298}