use rusqlite::Row;
use serde::{Deserialize, Serialize};
use uuid::Uuid;
pub const CODE_INDEX_UUID_NAMESPACE: Uuid = Uuid::from_bytes([
0xc0, 0xde, 0x1d, 0xe0, 0x00, 0x00, 0x40, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
]);
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Symbol {
pub id: String,
pub project_id: String,
pub file_path: String,
pub name: String,
pub qualified_name: String,
pub kind: String,
pub language: String,
pub byte_start: usize,
pub byte_end: usize,
pub line_start: usize,
pub line_end: usize,
#[serde(skip_serializing_if = "Option::is_none")]
pub signature: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub docstring: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub parent_symbol_id: Option<String>,
#[serde(default)]
pub content_hash: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub summary: Option<String>,
#[serde(default)]
pub created_at: String,
#[serde(default)]
pub updated_at: String,
}
impl Symbol {
pub fn make_id(
project_id: &str,
file_path: &str,
name: &str,
kind: &str,
byte_start: usize,
) -> String {
let key = format!("{project_id}:{file_path}:{name}:{kind}:{byte_start}");
Uuid::new_v5(&CODE_INDEX_UUID_NAMESPACE, key.as_bytes()).to_string()
}
pub fn from_row(row: &Row) -> rusqlite::Result<Self> {
let byte_start_raw = row.get::<_, i64>("byte_start")?;
let byte_end_raw = row.get::<_, i64>("byte_end")?;
let line_start_raw = row.get::<_, i64>("line_start")?;
let line_end_raw = row.get::<_, i64>("line_end")?;
let to_usize = |val: i64, col: usize| -> rusqlite::Result<usize> {
val.try_into()
.map_err(|_| rusqlite::Error::IntegralValueOutOfRange(col, val))
};
Ok(Self {
id: row.get("id")?,
project_id: row.get("project_id")?,
file_path: row.get("file_path")?,
name: row.get("name")?,
qualified_name: row.get("qualified_name")?,
kind: row.get("kind")?,
language: row.get("language")?,
byte_start: to_usize(byte_start_raw, 7)?,
byte_end: to_usize(byte_end_raw, 8)?,
line_start: to_usize(line_start_raw, 9)?,
line_end: to_usize(line_end_raw, 10)?,
signature: row.get("signature")?,
docstring: row.get("docstring")?,
parent_symbol_id: row.get("parent_symbol_id")?,
content_hash: row
.get::<_, Option<String>>("content_hash")?
.unwrap_or_default(),
summary: row.get("summary")?,
created_at: row
.get::<_, Option<String>>("created_at")?
.unwrap_or_default(),
updated_at: row
.get::<_, Option<String>>("updated_at")?
.unwrap_or_default(),
})
}
pub fn to_outline(&self) -> OutlineSymbol {
OutlineSymbol {
id: self.id.clone(),
name: self.name.clone(),
kind: self.kind.clone(),
line_start: self.line_start,
line_end: self.line_end,
signature: self.signature.clone(),
}
}
pub fn to_brief(&self) -> SearchResult {
SearchResult {
id: self.id.clone(),
name: self.name.clone(),
qualified_name: self.qualified_name.clone(),
kind: self.kind.clone(),
file_path: self.file_path.clone(),
line_start: self.line_start,
score: 0.0,
summary: self.summary.clone(),
signature: self.signature.clone(),
sources: None,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct IndexedFile {
pub id: String,
pub project_id: String,
pub file_path: String,
pub language: String,
pub content_hash: String,
pub symbol_count: usize,
pub byte_size: usize,
pub indexed_at: String,
}
impl IndexedFile {
pub fn make_id(project_id: &str, file_path: &str) -> String {
let key = format!("{project_id}:{file_path}");
Uuid::new_v5(&CODE_INDEX_UUID_NAMESPACE, key.as_bytes()).to_string()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ContentChunk {
pub id: String,
pub project_id: String,
pub file_path: String,
pub chunk_index: usize,
pub line_start: usize,
pub line_end: usize,
pub content: String,
pub language: String,
pub created_at: String,
}
impl ContentChunk {
pub fn make_id(project_id: &str, file_path: &str, chunk_index: usize) -> String {
let key = format!("{project_id}:{file_path}:chunk:{chunk_index}");
Uuid::new_v5(&CODE_INDEX_UUID_NAMESPACE, key.as_bytes()).to_string()
}
}
#[derive(Debug, Clone)]
pub struct ImportRelation {
pub file_path: String,
pub module_name: String,
}
#[derive(Debug, Clone)]
pub struct CallRelation {
pub caller_id: String,
pub callee_name: String,
pub file_path: String,
pub line: usize,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct IndexedProject {
pub id: String,
pub root_path: String,
pub total_files: usize,
pub total_symbols: usize,
pub last_indexed_at: String,
pub index_duration_ms: u64,
#[serde(skip_serializing_if = "Option::is_none")]
pub total_eligible_files: Option<usize>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SearchResult {
pub id: String,
pub name: String,
pub qualified_name: String,
pub kind: String,
pub file_path: String,
pub line_start: usize,
pub score: f64,
#[serde(skip_serializing_if = "Option::is_none")]
pub summary: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub signature: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub sources: Option<Vec<String>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GraphResult {
pub id: String,
pub name: String,
pub file_path: String,
pub line: usize,
#[serde(skip_serializing_if = "Option::is_none")]
pub relation: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub distance: Option<usize>,
}
pub struct ParseResult {
pub symbols: Vec<Symbol>,
pub imports: Vec<ImportRelation>,
pub calls: Vec<CallRelation>,
pub source: Vec<u8>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct IndexResult {
pub project_id: String,
pub files_indexed: usize,
pub files_skipped: usize,
pub symbols_found: usize,
pub errors: Vec<String>,
pub duration_ms: u64,
}
#[derive(Debug, Clone, Serialize)]
pub struct PagedResponse<T: Serialize> {
pub project_id: String,
pub total: usize,
pub offset: usize,
pub limit: usize,
pub results: Vec<T>,
#[serde(skip_serializing_if = "Option::is_none")]
pub hint: Option<String>,
}
#[derive(Debug, Clone, Serialize)]
pub struct OutlineSymbol {
pub id: String,
pub name: String,
pub kind: String,
pub line_start: usize,
pub line_end: usize,
#[serde(skip_serializing_if = "Option::is_none")]
pub signature: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ContentSearchHit {
pub file_path: String,
pub line_start: usize,
pub line_end: usize,
pub snippet: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub language: Option<String>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_uuid5_parity_with_python() {
let id = Symbol::make_id("proj1", "src/main.py", "foo", "function", 42);
let id2 = Symbol::make_id("proj1", "src/main.py", "foo", "function", 42);
assert_eq!(id, id2);
assert_eq!(
CODE_INDEX_UUID_NAMESPACE.to_string(),
"c0de1de0-0000-4000-8000-000000000000"
);
}
}