1use serde::{Deserialize, Serialize};
7use strum::{Display, EnumString};
8
9#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
11pub struct Span {
12 pub start_line: usize,
14 pub end_line: usize,
16}
17
18impl Span {
19 pub fn new(start_line: usize, start_col: usize, end_line: usize, end_col: usize) -> Self {
20 let _ = (start_col, end_col);
22 Self {
23 start_line,
24 end_line,
25 }
26 }
27}
28
29#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, EnumString, Display)]
31#[strum(serialize_all = "PascalCase")]
32pub enum SymbolKind {
33 Function,
34 Class,
35 Struct,
36 Enum,
37 Interface,
38 Trait,
39 Constant,
40 Variable,
41 Method,
42 Module,
43 Namespace,
44 Type,
45 Macro,
46 Property,
47 Event,
48 Import,
49 Export,
50 Attribute,
51 #[strum(default)]
55 Unknown(String),
56}
57
58#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)]
60#[serde(rename_all = "lowercase")]
61pub enum Language {
62 #[default]
63 Rust,
64 Python,
65 JavaScript,
66 TypeScript,
67 Vue,
68 Svelte,
69 Go,
70 Java,
71 PHP,
72 C,
73 Cpp,
74 CSharp,
75 Ruby,
76 Kotlin,
77 Swift,
78 Zig,
79 Unknown,
80}
81
82impl Language {
83 pub fn from_extension(ext: &str) -> Self {
84 match ext {
85 "rs" => Language::Rust,
86 "py" => Language::Python,
87 "js" | "mjs" | "cjs" | "jsx" => Language::JavaScript,
88 "ts" | "mts" | "cts" | "tsx" => Language::TypeScript,
89 "vue" => Language::Vue,
90 "svelte" => Language::Svelte,
91 "go" => Language::Go,
92 "java" => Language::Java,
93 "php" => Language::PHP,
94 "c" | "h" => Language::C,
95 "cpp" | "cc" | "cxx" | "hpp" | "hxx" | "C" | "H" => Language::Cpp,
96 "cs" => Language::CSharp,
97 "rb" | "rake" | "gemspec" => Language::Ruby,
98 "kt" | "kts" => Language::Kotlin,
99 "swift" => Language::Swift,
100 "zig" => Language::Zig,
101 _ => Language::Unknown,
102 }
103 }
104
105 pub fn from_name(name: &str) -> Option<Self> {
110 match name.to_lowercase().as_str() {
111 "rust" | "rs" => Some(Language::Rust),
112 "python" | "py" => Some(Language::Python),
113 "javascript" | "js" => Some(Language::JavaScript),
114 "typescript" | "ts" => Some(Language::TypeScript),
115 "vue" => Some(Language::Vue),
116 "svelte" => Some(Language::Svelte),
117 "go" => Some(Language::Go),
118 "java" => Some(Language::Java),
119 "php" => Some(Language::PHP),
120 "c" => Some(Language::C),
121 "cpp" | "c++" => Some(Language::Cpp),
122 "csharp" | "cs" | "c#" => Some(Language::CSharp),
123 "ruby" | "rb" => Some(Language::Ruby),
124 "kotlin" | "kt" => Some(Language::Kotlin),
125 "zig" => Some(Language::Zig),
126 _ => None,
127 }
128 }
129
130 pub fn supported_names_help() -> &'static str {
132 "rust (rs), python (py), javascript (js), typescript (ts), vue, svelte, \
133 go, java, php, c, cpp (c++), csharp (cs, c#), ruby (rb), kotlin (kt), zig"
134 }
135
136 pub fn is_supported(&self) -> bool {
141 match self {
142 Language::Rust => true,
143 Language::TypeScript => true,
144 Language::JavaScript => true,
145 Language::Vue => true,
146 Language::Svelte => true,
147 Language::Python => true,
148 Language::Go => true,
149 Language::Java => true,
150 Language::PHP => true,
151 Language::C => true,
152 Language::Cpp => true,
153 Language::CSharp => true,
154 Language::Ruby => true,
155 Language::Kotlin => true,
156 Language::Swift => false, Language::Zig => true,
158 Language::Unknown => false,
159 }
160 }
161}
162
163#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
165#[serde(rename_all = "lowercase")]
166pub enum ImportType {
167 Internal,
169 External,
171 Stdlib,
173 #[serde(rename = "mod_decl")]
175 ModDecl,
176}
177
178#[derive(Debug, Clone, Serialize, Deserialize)]
180pub struct DependencyInfo {
181 pub path: String,
183 #[serde(skip_serializing_if = "Option::is_none")]
185 pub line: Option<usize>,
186 #[serde(skip_serializing_if = "Option::is_none")]
188 pub symbols: Option<Vec<String>>,
189}
190
191#[derive(Debug, Clone)]
193pub struct Dependency {
194 pub file_id: i64,
196 pub imported_path: String,
198 pub resolved_file_id: Option<i64>,
200 pub import_type: ImportType,
202 pub line_number: usize,
204 pub imported_symbols: Option<Vec<String>>,
206}
207
208#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
215pub struct SymbolRef {
216 pub name: String,
218 pub kind: SymbolKind,
220 pub span: Span,
222}
223
224fn is_unknown_kind(kind: &SymbolKind) -> bool {
226 matches!(kind, SymbolKind::Unknown(_))
227}
228
229#[derive(Debug, Clone, Serialize, Deserialize)]
231pub struct SearchResult {
232 pub path: String,
234 #[serde(skip)]
236 pub lang: Language,
237 #[serde(skip_serializing_if = "is_unknown_kind")]
239 pub kind: SymbolKind,
240 #[serde(skip_serializing_if = "Option::is_none")]
243 pub symbol: Option<String>,
244 pub span: Span,
246 pub preview: String,
248 #[serde(skip_serializing_if = "Option::is_none")]
251 pub dependencies: Option<Vec<DependencyInfo>>,
252}
253
254#[derive(Debug, Clone, Serialize, Deserialize)]
256pub struct MatchResult {
257 #[serde(skip_serializing_if = "is_unknown_kind")]
259 pub kind: SymbolKind,
260 #[serde(skip_serializing_if = "Option::is_none")]
262 pub symbol: Option<String>,
263 pub span: Span,
265 pub preview: String,
267 #[serde(skip_serializing_if = "Vec::is_empty")]
269 pub context_before: Vec<String>,
270 #[serde(skip_serializing_if = "Vec::is_empty")]
272 pub context_after: Vec<String>,
273}
274
275#[derive(Debug, Clone, Serialize, Deserialize)]
277pub struct FileGroupedResult {
278 pub path: String,
280 pub language: Language,
282 #[serde(skip_serializing_if = "Option::is_none")]
284 pub dependencies: Option<Vec<DependencyInfo>>,
285 pub matches: Vec<MatchResult>,
287}
288
289impl SearchResult {
290 pub fn new(
291 path: String,
292 lang: Language,
293 kind: SymbolKind,
294 symbol: Option<String>,
295 span: Span,
296 scope: Option<String>,
297 preview: String,
298 ) -> Self {
299 let _ = scope;
301 Self {
302 path,
303 lang,
304 kind,
305 symbol,
306 span,
307 preview,
308 dependencies: None,
309 }
310 }
311}
312
313#[derive(Debug, Clone, Serialize, Deserialize)]
315pub struct IndexConfig {
316 pub languages: Vec<Language>,
318 pub include_patterns: Vec<String>,
320 pub exclude_patterns: Vec<String>,
322 pub follow_symlinks: bool,
324 pub max_file_size: usize,
326 pub parallel_threads: usize,
328 pub query_timeout_secs: u64,
330 pub max_posting_list_entries: usize,
333 #[serde(default, skip_serializing_if = "is_zero_u64")]
337 pub lock_wait_secs: u64,
338}
339
340impl Default for IndexConfig {
341 fn default() -> Self {
342 Self {
343 languages: vec![],
344 include_patterns: vec![],
345 exclude_patterns: vec![],
346 follow_symlinks: false,
347 max_file_size: 10 * 1024 * 1024, parallel_threads: 0, query_timeout_secs: 30, max_posting_list_entries: 500_000, lock_wait_secs: 0, }
353 }
354}
355
356fn is_zero(v: &usize) -> bool {
357 *v == 0
358}
359fn is_zero_u64(v: &u64) -> bool {
360 *v == 0
361}
362
363#[derive(Debug, Clone, Serialize, Deserialize, Default)]
365pub struct IndexStats {
366 pub total_files: usize,
368 pub index_size_bytes: u64,
370 pub last_updated: String,
372 pub files_by_language: std::collections::HashMap<String, usize>,
374 pub lines_by_language: std::collections::HashMap<String, usize>,
376 #[serde(default, skip_serializing_if = "is_zero")]
378 pub new_files: usize,
379 #[serde(default, skip_serializing_if = "is_zero")]
381 pub modified_files: usize,
382 #[serde(default, skip_serializing_if = "is_zero")]
384 pub unchanged_files: usize,
385 #[serde(default, skip_serializing_if = "is_zero")]
387 pub skipped_too_large: usize,
388 #[serde(default, skip_serializing_if = "is_zero_u64")]
390 pub skipped_bytes_too_large: u64,
391}
392
393#[derive(Debug, Clone, Serialize, Deserialize)]
395pub struct IndexedFile {
396 pub path: String,
398 pub language: String,
400 pub last_indexed: String,
402}
403
404#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
406#[serde(rename_all = "snake_case")]
407pub enum IndexStatus {
408 Fresh,
410 Stale,
412}
413
414#[derive(Debug, Clone, Serialize, Deserialize)]
416pub struct IndexWarning {
417 pub reason: String,
419 pub action_required: String,
421 #[serde(skip_serializing_if = "Option::is_none")]
423 pub files_modified: Option<u32>,
424 #[serde(skip_serializing_if = "Option::is_none")]
426 pub details: Option<IndexWarningDetails>,
427}
428
429#[derive(Debug, Clone, Serialize, Deserialize)]
431pub struct IndexWarningDetails {
432 #[serde(skip_serializing_if = "Option::is_none")]
434 pub current_branch: Option<String>,
435 #[serde(skip_serializing_if = "Option::is_none")]
437 pub indexed_branch: Option<String>,
438 #[serde(skip_serializing_if = "Option::is_none")]
440 pub current_commit: Option<String>,
441 #[serde(skip_serializing_if = "Option::is_none")]
443 pub indexed_commit: Option<String>,
444}
445
446#[derive(Debug, Clone, Serialize, Deserialize)]
448pub struct PaginationInfo {
449 pub total: usize,
451 pub count: usize,
453 pub offset: usize,
455 #[serde(skip_serializing_if = "Option::is_none")]
457 pub limit: Option<usize>,
458 pub has_more: bool,
460}
461
462#[derive(Debug, Clone, Serialize, Deserialize)]
464pub struct QueryResponse {
465 #[serde(skip_serializing_if = "Option::is_none")]
469 pub ai_instruction: Option<String>,
470 pub status: IndexStatus,
472 pub can_trust_results: bool,
474 #[serde(skip_serializing_if = "Option::is_none")]
476 pub warning: Option<IndexWarning>,
477 pub pagination: PaginationInfo,
479 pub results: Vec<FileGroupedResult>,
482}
483
484#[derive(Debug, Clone, Serialize, Deserialize)]
486pub struct CompactionReport {
487 pub files_removed: usize,
489 pub space_saved_bytes: u64,
491 pub duration_ms: u64,
493}
494
495#[cfg(test)]
496mod tests {
497 use super::*;
498
499 #[test]
500 fn test_symbol_ref_json_shape() {
501 let sym = SymbolRef {
502 name: "my_function".to_string(),
503 kind: SymbolKind::Function,
504 span: Span {
505 start_line: 10,
506 end_line: 20,
507 },
508 };
509 let json = serde_json::to_value(&sym).unwrap();
510 assert_eq!(json["name"], "my_function");
511 assert_eq!(json["kind"], "Function");
512 assert_eq!(json["span"]["start_line"], 10);
513 assert_eq!(json["span"]["end_line"], 20);
514 assert!(json.as_array().is_none());
515 }
516
517 #[test]
518 fn test_symbol_ref_roundtrip() {
519 let original = SymbolRef {
520 name: "MyStruct".to_string(),
521 kind: SymbolKind::Struct,
522 span: Span {
523 start_line: 1,
524 end_line: 5,
525 },
526 };
527 let json = serde_json::to_string(&original).unwrap();
528 let decoded: SymbolRef = serde_json::from_str(&json).unwrap();
529 assert_eq!(original, decoded);
530 }
531
532 #[test]
533 fn test_symbol_ref_exact_json() {
534 let sym = SymbolRef {
535 name: "Foo".to_string(),
536 kind: SymbolKind::Class,
537 span: Span {
538 start_line: 3,
539 end_line: 7,
540 },
541 };
542 let json = serde_json::to_string(&sym).unwrap();
543 assert_eq!(
544 json,
545 r#"{"name":"Foo","kind":"Class","span":{"start_line":3,"end_line":7}}"#
546 );
547 }
548}