use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ApiResponse<T> {
pub data: T,
pub success: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub error: Option<String>,
}
impl<T: Default> ApiResponse<T> {
pub fn success(data: T) -> Self {
Self {
data,
success: true,
error: None,
}
}
pub fn error(message: String) -> Self {
Self {
data: T::default(),
success: false,
error: Some(message),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EmptyResponse {}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CodebaseResponse {
pub id: String,
pub unique_project_id: String,
pub base_name: String,
pub path_hash: String,
pub instance: u32,
pub project_path: String,
pub display_name: String,
pub project_type: String,
pub last_indexed: String,
pub file_count: i64,
pub node_count: i64,
pub edge_count: i64,
pub is_valid: bool,
pub is_clone: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub cloned_from: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CodebaseListResponse {
pub codebases: Vec<CodebaseResponse>,
pub total: usize,
}
impl CodebaseListResponse {
pub fn empty() -> Self {
Self {
codebases: Vec::new(),
total: 0,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CodebaseDetailResponse {
pub codebase: CodebaseResponse,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct SyncReportResponse {
pub newly_discovered: usize,
pub updated: usize,
pub invalidated: usize,
pub missing: usize,
pub unchanged: usize,
pub errors: usize,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FileNode {
pub name: String,
pub path: String,
#[serde(rename = "type")]
pub node_type: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub size: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub last_modified: Option<String>,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub children: Vec<FileNode>,
}
impl FileNode {
pub fn file(name: String, path: String, size: u64) -> Self {
Self {
name,
path,
node_type: "file".to_string(),
size: Some(size),
last_modified: None,
children: Vec::new(),
}
}
pub fn directory(name: String, path: String) -> Self {
Self {
name,
path,
node_type: "directory".to_string(),
size: None,
last_modified: None,
children: Vec::new(),
}
}
pub fn add_child(&mut self, child: FileNode) {
self.children.push(child);
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FileTreeResponse {
pub tree: Vec<FileNode>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FileContentResponse {
pub path: String,
pub content: String,
pub encoding: String,
pub line_count: usize,
pub size: usize,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GraphNodeResponse {
pub id: String,
pub name: String,
#[serde(rename = "type")]
pub node_type: String,
pub val: u32,
pub color: String,
pub language: String,
pub complexity: u32,
pub file_path: String,
pub byte_range: [usize; 2],
#[serde(skip_serializing_if = "Option::is_none")]
pub x: Option<f32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub y: Option<f32>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GraphLinkResponse {
pub source: String,
pub target: String,
#[serde(rename = "type")]
pub link_type: String,
pub value: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GraphDataResponse {
pub nodes: Vec<GraphNodeResponse>,
pub links: Vec<GraphLinkResponse>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ScoreResponse {
pub semantic: f32,
pub text_match: f32,
pub structural: f32,
pub overall: f32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SearchResultResponse {
pub rank: usize,
pub node_id: String,
pub file_path: String,
pub symbol_name: String,
pub language: String,
pub score: ScoreResponse,
#[serde(skip_serializing_if = "Option::is_none")]
pub context: Option<String>,
pub byte_range: [usize; 2],
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SearchResultsResponse {
pub results: Vec<SearchResultResponse>,
}
impl SearchResultsResponse {
pub fn empty() -> Self {
Self {
results: Vec::new(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LanguageDistributionResponse {
pub language: String,
pub count: i64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DashboardCodebaseMetricsResponse {
pub id: String,
pub display_name: String,
pub project_path: String,
pub file_count: i64,
pub node_count: i64,
pub edge_count: i64,
pub import_edge_count: i64,
pub external_ref_count: i64,
pub dependency_link_count: i64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FeatureStatusResponse {
pub multi_project_enabled: bool,
pub cache_telemetry_enabled: bool,
pub external_dependency_resolution_enabled: bool,
pub context_aware_editing_enabled: bool,
pub bounded_impact_analysis_enabled: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CacheOverviewResponse {
pub analysis_cache_entries: i64,
pub temperature: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub estimated_hit_rate: Option<f64>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ExternalDependencyOverviewResponse {
pub external_refs: i64,
pub project_dependency_links: i64,
pub import_edges: i64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DashboardOverviewResponse {
pub generated_at: i64,
pub status: String,
pub total_codebases: usize,
pub total_files: i64,
pub total_nodes: i64,
pub total_edges: i64,
pub language_distribution: Vec<LanguageDistributionResponse>,
pub feature_status: FeatureStatusResponse,
pub cache: CacheOverviewResponse,
pub external_dependencies: ExternalDependencyOverviewResponse,
pub codebases: Vec<DashboardCodebaseMetricsResponse>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GraphNodeDetailResponse {
pub node: GraphNodeResponse,
pub neighbors: Vec<GraphNodeResponse>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct PhantomData {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_api_response_success() {
let response = ApiResponse::<String>::success("test data".to_string());
assert!(response.success);
assert_eq!(response.data, "test data");
assert!(response.error.is_none());
}
#[test]
fn test_api_response_error() {
let response = ApiResponse::<String>::error("error message".to_string());
assert!(!response.success);
assert_eq!(response.error, Some("error message".to_string()));
}
#[test]
fn test_codebase_list_response_empty() {
let response = CodebaseListResponse::empty();
assert_eq!(response.codebases.len(), 0);
assert_eq!(response.total, 0);
}
#[test]
fn test_file_node_file() {
let node = FileNode::file("test.rs".to_string(), "/path/to/test.rs".to_string(), 1024);
assert_eq!(node.name, "test.rs");
assert_eq!(node.node_type, "file");
assert_eq!(node.size, Some(1024));
assert!(node.children.is_empty());
}
#[test]
fn test_file_node_directory() {
let node = FileNode::directory("src".to_string(), "/path/to/src".to_string());
assert_eq!(node.name, "src");
assert_eq!(node.node_type, "directory");
assert!(node.size.is_none());
}
#[test]
fn test_file_node_add_child() {
let mut parent = FileNode::directory("src".to_string(), "/src".to_string());
let child = FileNode::file("lib.rs".to_string(), "/src/lib.rs".to_string(), 100);
parent.add_child(child);
assert_eq!(parent.children.len(), 1);
assert_eq!(parent.children[0].name, "lib.rs");
}
#[test]
fn test_sync_report_default() {
let report = SyncReportResponse::default();
assert_eq!(report.newly_discovered, 0);
assert_eq!(report.updated, 0);
assert_eq!(report.invalidated, 0);
assert_eq!(report.missing, 0);
assert_eq!(report.unchanged, 0);
assert_eq!(report.errors, 0);
}
#[test]
fn test_graph_data_empty() {
let response = GraphDataResponse {
nodes: vec![],
links: vec![],
};
assert!(response.nodes.is_empty());
assert!(response.links.is_empty());
}
#[test]
fn test_score_response() {
let score = ScoreResponse {
semantic: 0.95,
text_match: 0.8,
structural: 0.7,
overall: 0.85,
};
assert_eq!(score.semantic, 0.95);
assert_eq!(score.text_match, 0.8);
assert_eq!(score.structural, 0.7);
assert_eq!(score.overall, 0.85);
}
#[test]
fn test_search_results_empty() {
let response = SearchResultsResponse::empty();
assert!(response.results.is_empty());
}
}