Skip to main content

dendryform_core/
edge.rs

1//! Semantic edge (relationship) type.
2
3use serde::{Deserialize, Serialize};
4
5use crate::id::NodeId;
6use crate::kind::EdgeKind;
7
8/// A semantic relationship between two nodes.
9///
10/// Edges are not rendered visually in HTML (use connectors and flow labels
11/// for visual connections). They are used by Structurizr and Mermaid
12/// exporters to generate relationship diagrams.
13#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
14#[serde(rename_all = "snake_case")]
15pub struct Edge {
16    from: NodeId,
17    to: NodeId,
18    kind: EdgeKind,
19    #[serde(default, skip_serializing_if = "Option::is_none")]
20    label: Option<String>,
21}
22
23impl Edge {
24    /// Creates a new edge between two nodes.
25    pub fn new(from: NodeId, to: NodeId, kind: EdgeKind) -> Self {
26        Self {
27            from,
28            to,
29            kind,
30            label: None,
31        }
32    }
33
34    /// Creates a new edge with a descriptive label.
35    pub fn with_label(from: NodeId, to: NodeId, kind: EdgeKind, label: &str) -> Self {
36        Self {
37            from,
38            to,
39            kind,
40            label: Some(label.to_owned()),
41        }
42    }
43
44    /// Returns the source node ID.
45    pub fn from_id(&self) -> &NodeId {
46        &self.from
47    }
48
49    /// Returns the target node ID.
50    pub fn to_id(&self) -> &NodeId {
51        &self.to
52    }
53
54    /// Returns the relationship kind.
55    pub fn kind(&self) -> EdgeKind {
56        self.kind
57    }
58
59    /// Returns the optional label.
60    pub fn label(&self) -> Option<&str> {
61        self.label.as_deref()
62    }
63}
64
65#[cfg(test)]
66mod tests {
67    use super::*;
68
69    #[test]
70    fn test_new() {
71        let edge = Edge::new(
72            NodeId::new("a").unwrap(),
73            NodeId::new("b").unwrap(),
74            EdgeKind::Uses,
75        );
76        assert_eq!(edge.from_id().as_str(), "a");
77        assert_eq!(edge.to_id().as_str(), "b");
78        assert_eq!(edge.kind(), EdgeKind::Uses);
79        assert_eq!(edge.label(), None);
80    }
81
82    #[test]
83    fn test_with_label() {
84        let edge = Edge::with_label(
85            NodeId::new("api").unwrap(),
86            NodeId::new("db").unwrap(),
87            EdgeKind::Reads,
88            "SQL queries",
89        );
90        assert_eq!(edge.label(), Some("SQL queries"));
91    }
92
93    #[test]
94    fn test_serde_round_trip() {
95        let edge = Edge::with_label(
96            NodeId::new("api").unwrap(),
97            NodeId::new("db").unwrap(),
98            EdgeKind::Writes,
99            "inserts",
100        );
101        let json = serde_json::to_string(&edge).unwrap();
102        let deserialized: Edge = serde_json::from_str(&json).unwrap();
103        assert_eq!(edge, deserialized);
104    }
105
106    #[test]
107    fn test_serde_omits_none_label() {
108        let edge = Edge::new(
109            NodeId::new("a").unwrap(),
110            NodeId::new("b").unwrap(),
111            EdgeKind::Uses,
112        );
113        let json = serde_json::to_string(&edge).unwrap();
114        assert!(!json.contains("label"));
115    }
116}