Skip to main content

aster/map/server/
routes.rs

1//! API 路由处理
2//!
3//! 定义可视化服务器的 API 端点
4
5use std::collections::HashMap;
6use std::fs;
7use std::path::{Path, PathBuf};
8
9use crate::map::server::services::{
10    architecture::{build_architecture_map, get_module_detail, get_symbol_refs},
11    dependency::{build_dependency_tree, detect_entry_points},
12};
13use crate::map::server::types::*;
14use crate::map::types_enhanced::EnhancedCodeBlueprint;
15
16/// API 错误
17#[derive(Debug, Clone)]
18pub struct ApiError {
19    pub message: String,
20    pub status_code: u16,
21}
22
23impl ApiError {
24    pub fn not_found(msg: &str) -> Self {
25        Self {
26            message: msg.to_string(),
27            status_code: 404,
28        }
29    }
30
31    pub fn bad_request(msg: &str) -> Self {
32        Self {
33            message: msg.to_string(),
34            status_code: 400,
35        }
36    }
37
38    pub fn internal(msg: &str) -> Self {
39        Self {
40            message: msg.to_string(),
41            status_code: 500,
42        }
43    }
44}
45
46/// 检查是否为增强格式
47pub fn is_enhanced_format(data: &serde_json::Value) -> bool {
48    data.get("format").and_then(|v| v.as_str()) == Some("enhanced")
49        && data.get("modules").is_some()
50        && data.get("references").is_some()
51}
52
53/// 加载蓝图数据
54pub fn load_blueprint(ontology_path: &Path) -> Result<serde_json::Value, ApiError> {
55    let content = fs::read_to_string(ontology_path)
56        .map_err(|e| ApiError::internal(&format!("读取文件失败: {}", e)))?;
57    serde_json::from_str(&content)
58        .map_err(|e| ApiError::internal(&format!("解析 JSON 失败: {}", e)))
59}
60
61/// 加载增强蓝图
62pub fn load_enhanced_blueprint(ontology_path: &Path) -> Result<EnhancedCodeBlueprint, ApiError> {
63    let content = fs::read_to_string(ontology_path)
64        .map_err(|e| ApiError::internal(&format!("读取文件失败: {}", e)))?;
65    serde_json::from_str(&content).map_err(|e| ApiError::internal(&format!("解析蓝图失败: {}", e)))
66}
67
68/// 推断 map 目录
69pub fn infer_map_dir(ontology_path: &Path) -> PathBuf {
70    if ontology_path
71        .extension()
72        .map(|e| e == "json")
73        .unwrap_or(false)
74    {
75        ontology_path
76            .parent()
77            .unwrap_or(Path::new("."))
78            .join(".claude/map")
79    } else {
80        ontology_path.to_path_buf()
81    }
82}
83
84/// API 处理器集合
85pub struct ApiHandlers {
86    ontology_path: PathBuf,
87    map_dir: PathBuf,
88}
89
90impl ApiHandlers {
91    pub fn new(ontology_path: PathBuf) -> Self {
92        let map_dir = infer_map_dir(&ontology_path);
93        Self {
94            ontology_path,
95            map_dir,
96        }
97    }
98
99    /// 获取本体数据(chunked 模式的 index.json)
100    pub fn get_ontology(&self) -> Result<serde_json::Value, ApiError> {
101        let index_path = self.map_dir.join("index.json");
102        if index_path.exists() {
103            let content =
104                fs::read_to_string(&index_path).map_err(|e| ApiError::internal(&e.to_string()))?;
105            serde_json::from_str(&content).map_err(|e| ApiError::internal(&e.to_string()))
106        } else {
107            Err(ApiError::not_found(
108                "Blueprint not found. Please run /map generate first.",
109            ))
110        }
111    }
112
113    /// 获取 chunk 数据
114    pub fn get_chunk(&self, chunk_path: &str) -> Result<serde_json::Value, ApiError> {
115        // 安全性检查
116        if chunk_path.contains("..") || chunk_path.contains('~') {
117            return Err(ApiError::bad_request("Invalid chunk path"));
118        }
119
120        let chunk_file = self
121            .map_dir
122            .join("chunks")
123            .join(format!("{}.json", chunk_path));
124        if !chunk_file.exists() {
125            return Err(ApiError::not_found(&format!(
126                "Chunk not found: {}",
127                chunk_path
128            )));
129        }
130
131        let content =
132            fs::read_to_string(&chunk_file).map_err(|e| ApiError::internal(&e.to_string()))?;
133        serde_json::from_str(&content).map_err(|e| ApiError::internal(&e.to_string()))
134    }
135
136    /// 获取架构图数据
137    pub fn get_architecture(&self) -> Result<ArchitectureMap, ApiError> {
138        let blueprint = load_enhanced_blueprint(&self.ontology_path)?;
139        Ok(build_architecture_map(&blueprint))
140    }
141
142    /// 获取入口点列表
143    pub fn get_entry_points(&self) -> Result<EntryPointsResponse, ApiError> {
144        let blueprint = load_enhanced_blueprint(&self.ontology_path)?;
145        let entries = detect_entry_points(&blueprint);
146        Ok(EntryPointsResponse {
147            entry_points: entries,
148        })
149    }
150
151    /// 获取依赖树
152    pub fn get_dependency_tree(
153        &self,
154        entry_id: &str,
155        max_depth: usize,
156    ) -> Result<DependencyTreeNode, ApiError> {
157        let blueprint = load_enhanced_blueprint(&self.ontology_path)?;
158        build_dependency_tree(&blueprint, entry_id, max_depth)
159            .ok_or_else(|| ApiError::not_found("Entry module not found"))
160    }
161
162    /// 获取模块详情
163    pub fn get_module_detail(&self, module_id: &str) -> Result<ModuleDetailInfo, ApiError> {
164        let blueprint = load_enhanced_blueprint(&self.ontology_path)?;
165        get_module_detail(&blueprint, module_id)
166            .ok_or_else(|| ApiError::not_found("Module not found"))
167    }
168
169    /// 获取符号引用
170    pub fn get_symbol_refs(&self, symbol_id: &str) -> Result<SymbolRefInfo, ApiError> {
171        let blueprint = load_enhanced_blueprint(&self.ontology_path)?;
172        get_symbol_refs(&blueprint, symbol_id)
173            .ok_or_else(|| ApiError::not_found("Symbol not found"))
174    }
175
176    /// 搜索
177    pub fn search(&self, query: &str) -> Result<SearchResponse, ApiError> {
178        if query.is_empty() {
179            return Ok(SearchResponse {
180                results: Vec::new(),
181            });
182        }
183
184        let query_lower = query.to_lowercase();
185        let blueprint = load_enhanced_blueprint(&self.ontology_path)?;
186        let mut results: Vec<SearchResultItem> = Vec::new();
187
188        // 搜索模块
189        for module in blueprint.modules.values() {
190            if module.name.to_lowercase().contains(&query_lower)
191                || module.id.to_lowercase().contains(&query_lower)
192            {
193                results.push(SearchResultItem {
194                    result_type: "module".to_string(),
195                    id: module.id.clone(),
196                    name: module.name.clone(),
197                    module_id: None,
198                    description: module.semantic.as_ref().map(|s| s.description.clone()),
199                });
200            }
201        }
202
203        // 搜索符号
204        for symbol in blueprint.symbols.values() {
205            if symbol.name.to_lowercase().contains(&query_lower) {
206                let kind_str = format!("{:?}", symbol.kind).to_lowercase();
207                results.push(SearchResultItem {
208                    result_type: kind_str,
209                    id: symbol.id.clone(),
210                    name: symbol.name.clone(),
211                    module_id: Some(symbol.module_id.clone()),
212                    description: symbol.semantic.as_ref().map(|s| s.description.clone()),
213                });
214            }
215        }
216
217        results.truncate(50);
218        Ok(SearchResponse { results })
219    }
220
221    /// 获取所有 chunk 元数据
222    pub fn get_all_chunk_metadata(&self) -> Result<HashMap<String, ChunkMetadata>, ApiError> {
223        let chunks_dir = self.map_dir.join("chunks");
224        if !chunks_dir.exists() {
225            return Ok(HashMap::new());
226        }
227
228        let mut metadata: HashMap<String, ChunkMetadata> = HashMap::new();
229
230        let entries = fs::read_dir(&chunks_dir).map_err(|e| ApiError::internal(&e.to_string()))?;
231
232        for entry in entries.flatten() {
233            let path = entry.path();
234            if path.extension().map(|e| e == "json").unwrap_or(false) {
235                if let Some(file_name) = path.file_stem().and_then(|s| s.to_str()) {
236                    let dir_path = if file_name == "root" {
237                        String::new()
238                    } else {
239                        file_name.replace('_', "/")
240                    };
241
242                    if let Ok(meta) = fs::metadata(&path) {
243                        let modified = meta
244                            .modified()
245                            .map(|t| {
246                                t.duration_since(std::time::UNIX_EPOCH)
247                                    .map(|d| d.as_secs())
248                                    .unwrap_or(0)
249                            })
250                            .unwrap_or(0);
251
252                        metadata.insert(
253                            dir_path,
254                            ChunkMetadata {
255                                file: format!("chunks/{}.json", file_name),
256                                last_modified: modified,
257                                size: meta.len(),
258                                checksum: format!("{}-{}", meta.len(), modified),
259                            },
260                        );
261                    }
262                }
263            }
264        }
265
266        Ok(metadata)
267    }
268}
269
270/// Chunk 元数据
271#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
272#[serde(rename_all = "camelCase")]
273pub struct ChunkMetadata {
274    pub file: String,
275    pub last_modified: u64,
276    pub size: u64,
277    pub checksum: String,
278}