use serde::{Deserialize, Serialize};
use super::index::PROJECT_NAME_MAX_LENGTH;
use super::query::{QueryRequest, default_limit, default_min_score};
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "rag", derive(schemars::JsonSchema))]
pub struct AdvancedSearchRequest {
pub query: String,
#[serde(default)]
pub path: Option<String>,
#[serde(default)]
pub project: Option<String>,
#[serde(default = "default_limit")]
pub limit: usize,
#[serde(default = "default_min_score")]
pub min_score: f32,
#[serde(default)]
pub file_extensions: Vec<String>,
#[serde(default)]
pub languages: Vec<String>,
#[serde(default)]
pub path_patterns: Vec<String>,
}
impl AdvancedSearchRequest {
pub fn validate(&self) -> Result<(), String> {
let query_req = QueryRequest {
query: self.query.clone(),
path: None,
project: self.project.clone(),
limit: self.limit,
min_score: self.min_score,
hybrid: true,
};
query_req.validate()?;
for ext in &self.file_extensions {
if ext.is_empty() {
return Err("file extension cannot be empty".to_string());
}
if ext.len() > 20 {
return Err(format!(
"file extension too long: {} (max 20 characters)",
ext
));
}
}
for lang in &self.languages {
if lang.is_empty() {
return Err("language name cannot be empty".to_string());
}
if lang.len() > 50 {
return Err(format!(
"language name too long: {} (max 50 characters)",
lang
));
}
}
Ok(())
}
}
pub fn default_git_path() -> String {
".".to_string()
}
pub fn default_max_commits() -> usize {
10
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "rag", derive(schemars::JsonSchema))]
pub struct SearchGitHistoryRequest {
pub query: String,
#[serde(default = "default_git_path")]
pub path: String,
#[serde(default)]
pub project: Option<String>,
#[serde(default)]
pub branch: Option<String>,
#[serde(default = "default_max_commits")]
pub max_commits: usize,
#[serde(default = "default_limit")]
pub limit: usize,
#[serde(default = "default_min_score")]
pub min_score: f32,
#[serde(default)]
pub author: Option<String>,
#[serde(default)]
pub since: Option<String>,
#[serde(default)]
pub until: Option<String>,
#[serde(default)]
pub file_pattern: Option<String>,
}
impl SearchGitHistoryRequest {
pub fn validate(&self) -> Result<(), String> {
if self.query.trim().is_empty() {
return Err("query cannot be empty".to_string());
}
const MAX_QUERY_LENGTH: usize = 10_240; if self.query.len() > MAX_QUERY_LENGTH {
return Err(format!(
"query too long: {} bytes (max: {} bytes)",
self.query.len(),
MAX_QUERY_LENGTH
));
}
let path = std::path::Path::new(&self.path);
if !path.exists() {
return Err(format!("Path does not exist: {}", self.path));
}
if !(0.0..=1.0).contains(&self.min_score) {
return Err(format!(
"min_score must be between 0.0 and 1.0, got: {}",
self.min_score
));
}
const MAX_LIMIT: usize = 1000;
if self.limit > MAX_LIMIT {
return Err(format!(
"limit too large: {} (max: {})",
self.limit, MAX_LIMIT
));
}
const MAX_COMMITS_LIMIT: usize = 10000;
if self.max_commits > MAX_COMMITS_LIMIT {
return Err(format!(
"max_commits too large: {} (max: {})",
self.max_commits, MAX_COMMITS_LIMIT
));
}
if let Some(ref project) = self.project {
if project.is_empty() {
return Err("project name cannot be empty".to_string());
}
if project.len() > PROJECT_NAME_MAX_LENGTH {
return Err(format!(
"project name too long (max {} characters)",
PROJECT_NAME_MAX_LENGTH
));
}
}
Ok(())
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "rag", derive(schemars::JsonSchema))]
pub struct GitSearchResult {
pub commit_hash: String,
pub commit_message: String,
pub author: String,
pub author_email: String,
pub commit_date: i64,
pub score: f32,
pub vector_score: f32,
pub keyword_score: Option<f32>,
pub files_changed: Vec<String>,
pub diff_snippet: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "rag", derive(schemars::JsonSchema))]
pub struct SearchGitHistoryResponse {
pub results: Vec<GitSearchResult>,
pub commits_indexed: usize,
pub total_cached_commits: usize,
pub duration_ms: u64,
}