1use crate::graph::unified::concurrent::CodeGraph;
35use crate::graph::unified::node::NodeKind;
36use crate::graph::unified::storage::arena::NodeEntry;
37use serde::{Deserialize, Serialize};
38use std::path::Path;
39use std::sync::Arc;
40
41#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
57pub struct GraphNodeSummary {
58 #[serde(
60 serialize_with = "serialize_arc_str",
61 deserialize_with = "deserialize_arc_str"
62 )]
63 pub name: Arc<str>,
64
65 pub node_kind: NodeKind,
67
68 #[serde(
70 serialize_with = "serialize_arc_path",
71 deserialize_with = "deserialize_arc_path"
72 )]
73 pub file_path: Arc<Path>,
74
75 pub start_line: u32,
77
78 pub start_column: u32,
80
81 pub end_line: u32,
83
84 pub end_column: u32,
86
87 #[serde(
89 serialize_with = "serialize_option_arc_str",
90 deserialize_with = "deserialize_option_arc_str"
91 )]
92 pub qualified_name: Option<Arc<str>>,
93
94 #[serde(
96 serialize_with = "serialize_option_arc_str",
97 deserialize_with = "deserialize_option_arc_str"
98 )]
99 pub visibility: Option<Arc<str>>,
100
101 #[serde(
103 serialize_with = "serialize_option_arc_str",
104 deserialize_with = "deserialize_option_arc_str"
105 )]
106 pub signature: Option<Arc<str>>,
107
108 pub is_async: bool,
110
111 pub is_static: bool,
113}
114
115fn serialize_arc_str<S>(arc: &Arc<str>, serializer: S) -> Result<S::Ok, S::Error>
117where
118 S: serde::Serializer,
119{
120 serializer.serialize_str(arc)
121}
122
123fn deserialize_arc_str<'de, D>(deserializer: D) -> Result<Arc<str>, D::Error>
124where
125 D: serde::Deserializer<'de>,
126{
127 let s = String::deserialize(deserializer)?;
128 Ok(Arc::from(s.as_str()))
129}
130
131#[allow(clippy::ref_option)] fn serialize_option_arc_str<S>(opt: &Option<Arc<str>>, serializer: S) -> Result<S::Ok, S::Error>
134where
135 S: serde::Serializer,
136{
137 match opt {
138 Some(arc) => serializer.serialize_some(arc.as_ref()),
139 None => serializer.serialize_none(),
140 }
141}
142
143fn deserialize_option_arc_str<'de, D>(deserializer: D) -> Result<Option<Arc<str>>, D::Error>
144where
145 D: serde::Deserializer<'de>,
146{
147 let opt: Option<String> = Option::deserialize(deserializer)?;
148 Ok(opt.map(|s| Arc::from(s.as_str())))
149}
150
151fn serialize_arc_path<S>(arc: &Arc<Path>, serializer: S) -> Result<S::Ok, S::Error>
153where
154 S: serde::Serializer,
155{
156 serializer.serialize_str(arc.to_str().unwrap_or(""))
157}
158
159fn deserialize_arc_path<'de, D>(deserializer: D) -> Result<Arc<Path>, D::Error>
160where
161 D: serde::Deserializer<'de>,
162{
163 let s = String::deserialize(deserializer)?;
164 Ok(Arc::from(Path::new(&s)))
165}
166
167impl GraphNodeSummary {
168 #[must_use]
172 pub fn from_entry(entry: &NodeEntry, graph: &CodeGraph) -> Option<Self> {
173 let strings = graph.strings();
174 let files = graph.files();
175
176 let name = strings.resolve(entry.name)?;
177 let file_path = files.resolve(entry.file)?;
178 let qualified_name = entry
179 .qualified_name
180 .and_then(|id| strings.resolve(id))
181 .map(|value| Arc::from(value.as_ref()));
182 let visibility = entry
183 .visibility
184 .and_then(|id| strings.resolve(id))
185 .map(|value| Arc::from(value.as_ref()));
186 let signature = entry
187 .signature
188 .and_then(|id| strings.resolve(id))
189 .map(|value| Arc::from(value.as_ref()));
190
191 Some(Self {
192 name: Arc::from(name.as_ref()),
193 node_kind: entry.kind,
194 file_path: Arc::from(file_path.as_ref()),
195 start_line: entry.start_line,
196 start_column: entry.start_column,
197 end_line: entry.end_line,
198 end_column: entry.end_column,
199 qualified_name,
200 visibility,
201 signature,
202 is_async: entry.is_async,
203 is_static: entry.is_static,
204 })
205 }
206
207 #[must_use]
209 pub fn new(
210 name: impl Into<Arc<str>>,
211 node_kind: NodeKind,
212 file_path: impl Into<Arc<Path>>,
213 start_line: u32,
214 start_column: u32,
215 end_line: u32,
216 end_column: u32,
217 ) -> Self {
218 Self {
219 name: name.into(),
220 node_kind,
221 file_path: file_path.into(),
222 start_line,
223 start_column,
224 end_line,
225 end_column,
226 qualified_name: None,
227 visibility: None,
228 signature: None,
229 is_async: false,
230 is_static: false,
231 }
232 }
233
234 #[must_use]
242 pub fn serialized_size(&self) -> usize {
243 const BUDGET_FALLBACK: usize = 256;
244
245 match postcard::to_allocvec(self) {
246 Ok(bytes) => bytes.len(),
247 Err(e) => {
248 log::error!(
249 "Failed to serialize GraphNodeSummary for size calculation: {e}. Falling back to {BUDGET_FALLBACK} byte budget estimate."
250 );
251 BUDGET_FALLBACK
252 }
253 }
254 }
255}
256
257#[cfg(test)]
258mod tests {
259 use super::*;
260 use std::path::PathBuf;
261
262 #[test]
263 fn test_serialized_size_budget() {
264 let summary = GraphNodeSummary::new(
265 Arc::from("test_function"),
266 NodeKind::Function,
267 Arc::from(Path::new("src/lib.rs")),
268 10,
269 0,
270 12,
271 0,
272 );
273 assert!(summary.serialized_size() <= 256);
274 }
275
276 #[test]
277 fn test_roundtrip_serialization() {
278 let summary = GraphNodeSummary::new(
279 Arc::from("test_fn"),
280 NodeKind::Function,
281 Arc::from(Path::new("src/lib.rs")),
282 1,
283 0,
284 2,
285 10,
286 );
287
288 let bytes = postcard::to_allocvec(&summary).unwrap();
289 let restored: GraphNodeSummary = postcard::from_bytes(&bytes).unwrap();
290 assert_eq!(summary, restored);
291 }
292
293 #[test]
294 fn test_new_fields_defaults() {
295 let summary = GraphNodeSummary::new(
296 "test",
297 NodeKind::Function,
298 Arc::from(PathBuf::from("src/lib.rs").as_path()),
299 3,
300 1,
301 4,
302 2,
303 );
304
305 assert!(summary.qualified_name.is_none());
306 assert!(summary.visibility.is_none());
307 assert!(summary.signature.is_none());
308 assert!(!summary.is_async);
309 assert!(!summary.is_static);
310 }
311}