1use super::types::{CodeNode, GraphError, GraphStats};
7use crate::Project;
8use ryo_analysis::{AnalysisContext, SymbolId, SymbolKind, Visibility};
9use std::path::Path;
10use std::time::{Duration, Instant};
11
12pub struct GraphApi {
14 ctx: AnalysisContext,
15 build_time: Duration,
16}
17
18impl GraphApi {
19 pub fn from_path(path: &Path) -> Result<Self, GraphError> {
21 let start = Instant::now();
22 let project = Project::load(path).map_err(|e| GraphError::LoadFailed(e.to_string()))?;
23 Self::from_project(&project).map(|mut api| {
24 api.build_time = start.elapsed();
25 api
26 })
27 }
28
29 pub fn from_project(project: &Project) -> Result<Self, GraphError> {
31 let start = Instant::now();
32 let ctx = AnalysisContext::from_workspace_root_parallel(project.workspace_root())
33 .map_err(|e| GraphError::LoadFailed(format!("Failed to create context: {}", e)))?;
34 Ok(Self {
35 ctx,
36 build_time: start.elapsed(),
37 })
38 }
39
40 pub fn from_context(ctx: AnalysisContext, build_time: Duration) -> Self {
42 Self { ctx, build_time }
43 }
44
45 pub fn build_time(&self) -> Duration {
47 self.build_time
48 }
49
50 pub fn stats(&self) -> GraphStats {
52 let registry = &self.ctx.registry;
53
54 let mut functions = 0;
55 let mut structs = 0;
56 let mut enums = 0;
57 let mut traits = 0;
58 let mut impls = 0;
59
60 for (id, _) in registry.iter() {
61 if let Some(kind) = registry.kind(id) {
62 match kind {
63 SymbolKind::Function => functions += 1,
64 SymbolKind::Struct => structs += 1,
65 SymbolKind::Enum => enums += 1,
66 SymbolKind::Trait => traits += 1,
67 SymbolKind::Impl => impls += 1,
68 _ => {}
69 }
70 }
71 }
72
73 GraphStats {
74 files: self.ctx.files.len(),
75 nodes: self.ctx.code_graph.node_count(),
76 functions,
77 structs,
78 enums,
79 traits,
80 impls,
81 edges: self.ctx.code_graph.edge_count(),
82 }
83 }
84
85 pub fn context(&self) -> &AnalysisContext {
87 &self.ctx
88 }
89
90 pub(crate) fn query_nodes(&self, kind: SymbolKind) -> Vec<CodeNode> {
92 self.ctx
93 .registry
94 .iter()
95 .filter(|(id, _)| self.ctx.registry.kind(*id) == Some(kind))
96 .filter_map(|(id, _)| self.symbol_to_node(id))
97 .collect()
98 }
99
100 fn symbol_to_node(&self, id: SymbolId) -> Option<CodeNode> {
101 let path = self.ctx.registry.resolve(id)?;
102 let kind = self.ctx.registry.kind(id)?;
103
104 let file = self
105 .ctx
106 .registry
107 .span(id)
108 .map(|s| s.file.to_absolute())
109 .unwrap_or_default();
110
111 let is_public = self
112 .ctx
113 .registry
114 .visibility(id)
115 .map(|v| *v == Visibility::Public)
116 .unwrap_or(false);
117
118 let is_async = self
119 .ctx
120 .detail_store
121 .function(id)
122 .map(|f| f.is_async)
123 .unwrap_or(false);
124
125 Some(CodeNode {
126 name: path.name().to_string(),
127 file,
128 kind: kind.into(),
129 is_public,
130 is_async,
131 symbol_id: id,
132 })
133 }
134}
135
136#[cfg(test)]
137mod tests {
138 use super::*;
139 use std::fs;
140 use tempfile::tempdir;
141
142 fn create_test_project() -> tempfile::TempDir {
143 let dir = tempdir().unwrap();
144 let src = dir.path().join("src");
145 fs::create_dir(&src).unwrap();
146
147 fs::write(
148 dir.path().join("Cargo.toml"),
149 r#"[package]
150name = "test-project"
151version = "0.1.0"
152edition = "2021"
153"#,
154 )
155 .unwrap();
156
157 fs::write(
158 src.join("lib.rs"),
159 r#"
160pub fn greet(name: &str) -> String {
161 format!("Hello, {}!", name)
162}
163
164pub struct User {
165 pub name: String,
166}
167
168pub enum Status {
169 Active,
170 Inactive,
171}
172
173pub trait Greetable {
174 fn greet(&self) -> String;
175}
176"#,
177 )
178 .unwrap();
179
180 dir
181 }
182
183 #[test]
184 fn test_from_path() {
185 let dir = create_test_project();
186 let result = GraphApi::from_path(dir.path());
187 assert!(result.is_ok());
188 }
189
190 #[test]
191 fn test_stats() {
192 let dir = create_test_project();
193 let api = GraphApi::from_path(dir.path()).unwrap();
194 let stats = api.stats();
195
196 assert_eq!(stats.files, 1);
197 assert!(stats.nodes > 0);
198 }
199
200 #[test]
201 fn test_build_time() {
202 let dir = create_test_project();
203 let api = GraphApi::from_path(dir.path()).unwrap();
204 assert!(api.build_time().as_nanos() > 0);
205 }
206}