1use serde::{Deserialize, Serialize};
2
3use crate::error::ParseEnumError;
4use crate::ids::{BranchId, EdgeId, NodeId};
5
6#[derive(Debug, Clone, Serialize, Deserialize)]
8#[serde(rename_all = "snake_case")]
9pub struct Edge {
10 pub id: EdgeId,
11 pub source_id: NodeId,
12 pub target_id: NodeId,
13 pub edge_type: EdgeType,
14 pub branch_id: BranchId,
15 pub weight: f64,
16 #[serde(skip_serializing_if = "Option::is_none")]
18 pub metadata: Option<serde_json::Value>,
19}
20
21#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
23#[serde(rename_all = "snake_case")]
24pub enum EdgeType {
25 RelatedTo,
27 Updates,
29 Contradicts,
31 PartOf,
33 DependsOn,
35 Implements,
37}
38
39impl EdgeType {
40 pub fn as_str(&self) -> &'static str {
42 match self {
43 Self::RelatedTo => "related_to",
44 Self::Updates => "updates",
45 Self::Contradicts => "contradicts",
46 Self::PartOf => "part_of",
47 Self::DependsOn => "depends_on",
48 Self::Implements => "implements",
49 }
50 }
51}
52
53impl std::fmt::Display for EdgeType {
54 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
55 match self {
56 Self::RelatedTo => write!(f, "RelatedTo"),
57 Self::Updates => write!(f, "Updates"),
58 Self::Contradicts => write!(f, "Contradicts"),
59 Self::PartOf => write!(f, "PartOf"),
60 Self::DependsOn => write!(f, "DependsOn"),
61 Self::Implements => write!(f, "Implements"),
62 }
63 }
64}
65
66impl std::str::FromStr for EdgeType {
67 type Err = ParseEnumError;
68
69 fn from_str(s: &str) -> Result<Self, Self::Err> {
70 match s {
71 "related_to" => Ok(Self::RelatedTo),
72 "updates" => Ok(Self::Updates),
73 "contradicts" => Ok(Self::Contradicts),
74 "part_of" => Ok(Self::PartOf),
75 "depends_on" => Ok(Self::DependsOn),
76 "implements" => Ok(Self::Implements),
77 _ => Err(ParseEnumError {
78 type_name: "EdgeType",
79 value: s.to_owned(),
80 }),
81 }
82 }
83}
84
85#[cfg(test)]
86mod tests {
87 use super::*;
88 use crate::ids::{BranchId, EdgeId, NodeId};
89
90 #[test]
91 fn edge_serialization_roundtrip() {
92 let edge = Edge {
93 id: EdgeId(1),
94 source_id: NodeId(10),
95 target_id: NodeId(20),
96 edge_type: EdgeType::DependsOn,
97 branch_id: BranchId::from("main"),
98 weight: 1.0,
99 metadata: None,
100 };
101
102 let json = serde_json::to_string(&edge).expect("serialize");
103 assert!(
104 !json.contains("metadata"),
105 "None metadata should be skipped"
106 );
107
108 let deserialized: Edge = serde_json::from_str(&json).expect("deserialize");
109 assert_eq!(deserialized.edge_type, EdgeType::DependsOn);
110 assert_eq!(deserialized.source_id, NodeId(10));
111 assert_eq!(deserialized.target_id, NodeId(20));
112 }
113
114 #[test]
115 fn all_edge_type_variants() {
116 let types = [
117 EdgeType::RelatedTo,
118 EdgeType::Updates,
119 EdgeType::Contradicts,
120 EdgeType::PartOf,
121 EdgeType::DependsOn,
122 EdgeType::Implements,
123 ];
124 assert_eq!(types.len(), 6);
125 }
126
127 #[test]
128 fn edge_type_display() {
129 assert_eq!(EdgeType::DependsOn.to_string(), "DependsOn");
130 assert_eq!(EdgeType::Contradicts.to_string(), "Contradicts");
131 }
132
133 #[test]
134 fn edge_type_roundtrip_str() {
135 let types = [
136 EdgeType::RelatedTo,
137 EdgeType::Updates,
138 EdgeType::Contradicts,
139 EdgeType::PartOf,
140 EdgeType::DependsOn,
141 EdgeType::Implements,
142 ];
143 for et in types {
144 let s = et.as_str();
145 let parsed: EdgeType = s.parse().unwrap();
146 assert_eq!(parsed, et);
147 }
148 }
149
150 #[test]
151 fn edge_type_parse_unknown() {
152 assert!("bogus".parse::<EdgeType>().is_err());
153 }
154}