1use crate::{Edge, Node};
8use serde::{Deserialize, Serialize};
9
10#[derive(Clone, Debug, Serialize, Deserialize)]
16pub struct NodeSummary {
17 pub id: String,
19 pub title: String,
21 pub category: Option<String>,
23 pub description: Option<String>,
25}
26
27impl From<&Node> for NodeSummary {
28 fn from(node: &Node) -> Self {
29 Self {
30 id: node.id.clone(),
31 title: node.title.clone(),
32 category: node.category.clone(),
33 description: node
34 .metadata
35 .get("description")
36 .and_then(|v| v.as_str())
37 .map(String::from),
38 }
39 }
40}
41
42#[derive(Clone, Debug, Serialize, Deserialize)]
44pub struct EdgeInfo {
45 pub from: String,
47 pub to: String,
49 pub relationship: String,
51 pub weight: f32,
53}
54
55impl From<&Edge> for EdgeInfo {
56 fn from(edge: &Edge) -> Self {
57 Self {
58 from: edge.from.clone(),
59 to: edge.to.clone(),
60 relationship: edge.relationship.name().to_string(),
61 weight: edge.weight,
62 }
63 }
64}
65
66#[derive(Clone, Debug, Serialize, Deserialize)]
72pub struct RelatedConceptsResponse {
73 pub source: NodeSummary,
75 pub related: Vec<RelatedGroup>,
77 pub total_count: usize,
79}
80
81#[derive(Clone, Debug, Serialize, Deserialize)]
83pub struct RelatedGroup {
84 pub relationship: String,
86 pub concepts: Vec<NodeSummary>,
88}
89
90#[derive(Clone, Debug, Serialize, Deserialize)]
96pub struct PathResponse {
97 pub from: NodeSummary,
99 pub to: NodeSummary,
101 pub path: Vec<PathStep>,
103 pub found: bool,
105 pub length: usize,
107}
108
109#[derive(Clone, Debug, Serialize, Deserialize)]
111pub struct PathStep {
112 pub node: NodeSummary,
114 pub relationship_to_next: Option<String>,
116}
117
118#[derive(Clone, Debug, Serialize, Deserialize)]
124pub struct PrerequisitesResponse {
125 pub target: NodeSummary,
127 pub prerequisites: Vec<PrerequisiteInfo>,
129 pub count: usize,
131 pub has_cycles: bool,
133}
134
135#[derive(Clone, Debug, Serialize, Deserialize)]
137pub struct PrerequisiteInfo {
138 pub node: NodeSummary,
140 pub depth: usize,
142}
143
144#[derive(Clone, Debug, Serialize, Deserialize)]
150pub struct NeighborhoodResponse {
151 pub center: NodeSummary,
153 pub nodes: Vec<NeighborInfo>,
155 pub edges: Vec<EdgeInfo>,
157 pub radius: usize,
159}
160
161#[derive(Clone, Debug, Serialize, Deserialize)]
163pub struct NeighborInfo {
164 pub node: NodeSummary,
166 pub distance: usize,
168}
169
170#[derive(Clone, Debug, Serialize, Deserialize)]
176pub struct GraphInfoResponse {
177 pub node_count: usize,
179 pub edge_count: usize,
181 pub categories: Vec<CategoryCount>,
183 pub relationships: Vec<RelationshipCount>,
185}
186
187#[derive(Clone, Debug, Serialize, Deserialize)]
189pub struct CategoryCount {
190 pub category: String,
192 pub count: usize,
194}
195
196#[derive(Clone, Debug, Serialize, Deserialize)]
198pub struct RelationshipCount {
199 pub relationship: String,
201 pub count: usize,
203}
204
205#[cfg(test)]
210mod tests {
211 use super::*;
212 use crate::types::*;
213
214 #[test]
215 fn test_node_summary_from_node() {
216 let node = Node::new("test-id", "Test Title")
217 .with_category("test-cat")
218 .with_metadata("description", "A test concept");
219
220 let summary = NodeSummary::from(&node);
221
222 assert_eq!(summary.id, "test-id");
223 assert_eq!(summary.title, "Test Title");
224 assert_eq!(summary.category, Some("test-cat".to_string()));
225 assert_eq!(summary.description, Some("A test concept".to_string()));
226 }
227
228 #[test]
229 fn test_node_summary_from_node_no_description() {
230 let node = Node::new("x", "X");
231 let summary = NodeSummary::from(&node);
232
233 assert_eq!(summary.id, "x");
234 assert!(summary.description.is_none());
235 assert!(summary.category.is_none());
236 }
237
238 #[test]
239 fn test_edge_info_from_edge() {
240 let edge = Edge::new("a", "b", Relationship::Prerequisite).with_weight(0.8);
241 let info = EdgeInfo::from(&edge);
242
243 assert_eq!(info.from, "a");
244 assert_eq!(info.to, "b");
245 assert_eq!(info.relationship, "prerequisite");
246 assert_eq!(info.weight, 0.8);
247 }
248
249 #[test]
250 fn test_edge_info_custom_relationship() {
251 let edge = Edge::new("a", "b", Relationship::Custom("implies".to_string()));
252 let info = EdgeInfo::from(&edge);
253
254 assert_eq!(info.relationship, "implies");
255 }
256
257 #[test]
258 fn test_node_summary_serialization() {
259 let summary = NodeSummary {
260 id: "test".to_string(),
261 title: "Test".to_string(),
262 category: Some("cat".to_string()),
263 description: None,
264 };
265
266 let json = serde_json::to_string(&summary).unwrap();
267 let parsed: NodeSummary = serde_json::from_str(&json).unwrap();
268
269 assert_eq!(parsed.id, "test");
270 assert_eq!(parsed.category, Some("cat".to_string()));
271 }
272
273 #[test]
274 fn test_edge_info_serialization() {
275 let info = EdgeInfo {
276 from: "a".to_string(),
277 to: "b".to_string(),
278 relationship: "prerequisite".to_string(),
279 weight: 1.0,
280 };
281
282 let json = serde_json::to_string(&info).unwrap();
283 let parsed: EdgeInfo = serde_json::from_str(&json).unwrap();
284
285 assert_eq!(parsed.from, "a");
286 assert_eq!(parsed.weight, 1.0);
287 }
288
289 #[test]
290 fn test_related_concepts_response_serialization() {
291 let response = RelatedConceptsResponse {
292 source: NodeSummary {
293 id: "src".to_string(),
294 title: "Source".to_string(),
295 category: None,
296 description: None,
297 },
298 related: vec![RelatedGroup {
299 relationship: "prerequisite".to_string(),
300 concepts: vec![NodeSummary {
301 id: "dep".to_string(),
302 title: "Dependency".to_string(),
303 category: None,
304 description: None,
305 }],
306 }],
307 total_count: 1,
308 };
309
310 let json = serde_json::to_string(&response).unwrap();
311 let parsed: RelatedConceptsResponse = serde_json::from_str(&json).unwrap();
312
313 assert_eq!(parsed.total_count, 1);
314 assert_eq!(parsed.related.len(), 1);
315 assert_eq!(parsed.related[0].concepts.len(), 1);
316 }
317
318 #[test]
319 fn test_path_response_serialization() {
320 let response = PathResponse {
321 from: NodeSummary {
322 id: "a".to_string(),
323 title: "A".to_string(),
324 category: None,
325 description: None,
326 },
327 to: NodeSummary {
328 id: "c".to_string(),
329 title: "C".to_string(),
330 category: None,
331 description: None,
332 },
333 path: vec![
334 PathStep {
335 node: NodeSummary {
336 id: "a".to_string(),
337 title: "A".to_string(),
338 category: None,
339 description: None,
340 },
341 relationship_to_next: Some("prerequisite".to_string()),
342 },
343 PathStep {
344 node: NodeSummary {
345 id: "c".to_string(),
346 title: "C".to_string(),
347 category: None,
348 description: None,
349 },
350 relationship_to_next: None,
351 },
352 ],
353 found: true,
354 length: 2,
355 };
356
357 let json = serde_json::to_string(&response).unwrap();
358 let parsed: PathResponse = serde_json::from_str(&json).unwrap();
359
360 assert!(parsed.found);
361 assert_eq!(parsed.path.len(), 2);
362 }
363
364 #[test]
365 fn test_prerequisites_response_serialization() {
366 let response = PrerequisitesResponse {
367 target: NodeSummary {
368 id: "target".to_string(),
369 title: "Target".to_string(),
370 category: None,
371 description: None,
372 },
373 prerequisites: vec![PrerequisiteInfo {
374 node: NodeSummary {
375 id: "prereq".to_string(),
376 title: "Prereq".to_string(),
377 category: None,
378 description: None,
379 },
380 depth: 1,
381 }],
382 count: 1,
383 has_cycles: false,
384 };
385
386 let json = serde_json::to_string(&response).unwrap();
387 let parsed: PrerequisitesResponse = serde_json::from_str(&json).unwrap();
388
389 assert_eq!(parsed.count, 1);
390 assert!(!parsed.has_cycles);
391 }
392
393 #[test]
394 fn test_neighborhood_response_serialization() {
395 let response = NeighborhoodResponse {
396 center: NodeSummary {
397 id: "center".to_string(),
398 title: "Center".to_string(),
399 category: None,
400 description: None,
401 },
402 nodes: vec![NeighborInfo {
403 node: NodeSummary {
404 id: "neighbor".to_string(),
405 title: "Neighbor".to_string(),
406 category: None,
407 description: None,
408 },
409 distance: 1,
410 }],
411 edges: vec![EdgeInfo {
412 from: "center".to_string(),
413 to: "neighbor".to_string(),
414 relationship: "relates_to".to_string(),
415 weight: 0.7,
416 }],
417 radius: 2,
418 };
419
420 let json = serde_json::to_string(&response).unwrap();
421 let parsed: NeighborhoodResponse = serde_json::from_str(&json).unwrap();
422
423 assert_eq!(parsed.radius, 2);
424 assert_eq!(parsed.nodes.len(), 1);
425 assert_eq!(parsed.edges.len(), 1);
426 }
427
428 #[test]
429 fn test_graph_info_response_serialization() {
430 let response = GraphInfoResponse {
431 node_count: 42,
432 edge_count: 100,
433 categories: vec![CategoryCount {
434 category: "harmony".to_string(),
435 count: 15,
436 }],
437 relationships: vec![RelationshipCount {
438 relationship: "prerequisite".to_string(),
439 count: 50,
440 }],
441 };
442
443 let json = serde_json::to_string(&response).unwrap();
444 let parsed: GraphInfoResponse = serde_json::from_str(&json).unwrap();
445
446 assert_eq!(parsed.node_count, 42);
447 assert_eq!(parsed.edge_count, 100);
448 assert_eq!(parsed.categories.len(), 1);
449 assert_eq!(parsed.relationships.len(), 1);
450 }
451}