Skip to main content

cargo_perspective/
types.rs

1//! Core data types for dependency analysis and visualization.
2
3use serde::{Deserialize, Serialize};
4use std::collections::HashMap;
5
6/// Represents a method or function call within a crate.
7#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
8pub struct MethodCall {
9    /// Full path to the method (e.g., "tokio::io::AsyncReadExt::read")
10    pub path: String,
11    /// Number of times this method is called
12    pub call_count: usize,
13    /// Average execution time in nanoseconds (optional, for v0.2+)
14    pub avg_time_ns: Option<u64>,
15    /// Source locations where this method is called
16    pub call_sites: Vec<CallSite>,
17}
18
19/// Represents a location where a method is called.
20#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
21pub struct CallSite {
22    pub file_path: String,
23    pub line: usize,
24    pub column: usize,
25}
26
27/// Represents a crate with all its method calls.
28#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
29pub struct CrateUsage {
30    /// Name of the crate
31    pub name: String,
32    /// Whether this is a direct or transitive dependency
33    pub is_direct: bool,
34    /// Total number of calls to this crate
35    pub total_calls: usize,
36    /// Map of method path -> method call data
37    pub methods: HashMap<String, MethodCall>,
38}
39
40impl CrateUsage {
41    pub fn new(name: String, is_direct: bool) -> Self {
42        Self {
43            name,
44            is_direct,
45            total_calls: 0,
46            methods: HashMap::new(),
47        }
48    }
49
50    /// Add a method call to this crate.
51    pub fn add_call(&mut self, method_path: String, call_site: CallSite) {
52        self.total_calls += 1;
53
54        let method = self.methods.entry(method_path.clone()).or_insert_with(|| {
55            MethodCall {
56                path: method_path,
57                call_count: 0,
58                avg_time_ns: None,
59                call_sites: Vec::new(),
60            }
61        });
62
63        method.call_count += 1;
64        method.call_sites.push(call_site);
65    }
66}
67
68/// Complete analysis results for a Rust project.
69#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
70pub struct ProjectAnalysis {
71    /// Name of the root crate
72    pub root_crate: String,
73    /// Map of crate name -> crate usage
74    pub crates: HashMap<String, CrateUsage>,
75    /// Total number of dependency calls across the project
76    pub total_calls: usize,
77}
78
79impl ProjectAnalysis {
80    pub fn new(root_crate: String) -> Self {
81        Self {
82            root_crate,
83            crates: HashMap::new(),
84            total_calls: 0,
85        }
86    }
87
88    /// Get or create a crate usage entry.
89    pub fn get_or_create_crate(&mut self, name: String, is_direct: bool) -> &mut CrateUsage {
90        self.crates
91            .entry(name.clone())
92            .or_insert_with(|| CrateUsage::new(name, is_direct))
93    }
94
95    /// Calculate total calls across all crates.
96    pub fn recalculate_totals(&mut self) {
97        self.total_calls = self.crates.values().map(|c| c.total_calls).sum();
98    }
99}
100
101/// Rectangle for TreeMap layout.
102#[derive(Debug, Clone, Copy, PartialEq)]
103pub struct TreeMapRect {
104    pub x: f64,
105    pub y: f64,
106    pub width: f64,
107    pub height: f64,
108}
109
110impl TreeMapRect {
111    pub fn new(x: f64, y: f64, width: f64, height: f64) -> Self {
112        Self { x, y, width, height }
113    }
114
115    pub fn area(&self) -> f64 {
116        self.width * self.height
117    }
118
119    pub fn aspect_ratio(&self) -> f64 {
120        let min = self.width.min(self.height);
121        let max = self.width.max(self.height);
122        if min == 0.0 {
123            f64::INFINITY
124        } else {
125            max / min
126        }
127    }
128}
129
130/// A node in the TreeMap representing either a crate or a method.
131#[derive(Debug, Clone, PartialEq)]
132pub struct TreeMapNode {
133    /// Label for this node
134    pub label: String,
135    /// Size value (call count)
136    pub value: usize,
137    /// Optional performance metric (execution time)
138    pub time_ns: Option<u64>,
139    /// Child nodes (for crates containing methods)
140    pub children: Vec<TreeMapNode>,
141    /// Layout rectangle (computed during layout)
142    pub rect: Option<TreeMapRect>,
143}
144
145impl TreeMapNode {
146    pub fn new_crate(name: String, value: usize, children: Vec<TreeMapNode>) -> Self {
147        Self {
148            label: name,
149            value,
150            time_ns: None,
151            children,
152            rect: None,
153        }
154    }
155
156    pub fn new_method(path: String, call_count: usize, time_ns: Option<u64>) -> Self {
157        Self {
158            label: path,
159            value: call_count,
160            time_ns,
161            children: Vec::new(),
162            rect: None,
163        }
164    }
165
166    /// Convert ProjectAnalysis to TreeMap structure.
167    pub fn from_analysis(analysis: &ProjectAnalysis) -> Vec<TreeMapNode> {
168        let mut nodes = Vec::new();
169
170        for (crate_name, crate_usage) in &analysis.crates {
171            let method_nodes: Vec<TreeMapNode> = crate_usage
172                .methods
173                .values()
174                .map(|method| {
175                    TreeMapNode::new_method(
176                        method.path.clone(),
177                        method.call_count,
178                        method.avg_time_ns,
179                    )
180                })
181                .collect();
182
183            nodes.push(TreeMapNode::new_crate(
184                crate_name.clone(),
185                crate_usage.total_calls,
186                method_nodes,
187            ));
188        }
189
190        // Sort by value descending for better visualization
191        nodes.sort_by(|a, b| b.value.cmp(&a.value));
192        nodes
193    }
194}