Skip to main content

dendryform_core/
kind.rs

1//! Semantic kind enums for nodes and edges.
2
3use std::fmt;
4
5use serde::{Deserialize, Serialize};
6
7/// The semantic type of a node in the architecture diagram.
8///
9/// These map to C4 model concepts for export compatibility.
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
11#[serde(rename_all = "snake_case")]
12#[non_exhaustive]
13pub enum NodeKind {
14    /// A human user of the system.
15    Person,
16    /// A top-level software system.
17    System,
18    /// A deployable unit within a system.
19    Container,
20    /// A module or service within a container.
21    Component,
22    /// Infrastructure (databases, message queues, cloud services).
23    Infrastructure,
24    /// A logical grouping of related elements.
25    Group,
26}
27
28impl fmt::Display for NodeKind {
29    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
30        match self {
31            Self::Person => f.write_str("person"),
32            Self::System => f.write_str("system"),
33            Self::Container => f.write_str("container"),
34            Self::Component => f.write_str("component"),
35            Self::Infrastructure => f.write_str("infrastructure"),
36            Self::Group => f.write_str("group"),
37        }
38    }
39}
40
41/// The semantic type of a relationship between nodes.
42///
43/// Used by Structurizr and Mermaid exporters to label edges.
44#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
45#[serde(rename_all = "snake_case")]
46#[non_exhaustive]
47pub enum EdgeKind {
48    /// General dependency or invocation.
49    Uses,
50    /// Read access to a data source.
51    Reads,
52    /// Write access to a data source.
53    Writes,
54    /// Deployment relationship.
55    Deploys,
56    /// Containment relationship.
57    Contains,
58}
59
60impl fmt::Display for EdgeKind {
61    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
62        match self {
63            Self::Uses => f.write_str("uses"),
64            Self::Reads => f.write_str("reads"),
65            Self::Writes => f.write_str("writes"),
66            Self::Deploys => f.write_str("deploys"),
67            Self::Contains => f.write_str("contains"),
68        }
69    }
70}
71
72#[cfg(test)]
73mod tests {
74    use super::*;
75
76    #[test]
77    fn test_node_kind_display_all() {
78        assert_eq!(format!("{}", NodeKind::Person), "person");
79        assert_eq!(format!("{}", NodeKind::System), "system");
80        assert_eq!(format!("{}", NodeKind::Container), "container");
81        assert_eq!(format!("{}", NodeKind::Component), "component");
82        assert_eq!(format!("{}", NodeKind::Infrastructure), "infrastructure");
83        assert_eq!(format!("{}", NodeKind::Group), "group");
84    }
85
86    #[test]
87    fn test_edge_kind_display_all() {
88        assert_eq!(format!("{}", EdgeKind::Uses), "uses");
89        assert_eq!(format!("{}", EdgeKind::Reads), "reads");
90        assert_eq!(format!("{}", EdgeKind::Writes), "writes");
91        assert_eq!(format!("{}", EdgeKind::Deploys), "deploys");
92        assert_eq!(format!("{}", EdgeKind::Contains), "contains");
93    }
94
95    #[test]
96    fn test_node_kind_serde_all_variants() {
97        let variants = vec![
98            (NodeKind::Person, "\"person\""),
99            (NodeKind::System, "\"system\""),
100            (NodeKind::Container, "\"container\""),
101            (NodeKind::Component, "\"component\""),
102            (NodeKind::Infrastructure, "\"infrastructure\""),
103            (NodeKind::Group, "\"group\""),
104        ];
105        for (kind, expected_json) in variants {
106            let json = serde_json::to_string(&kind).unwrap();
107            assert_eq!(json, expected_json);
108            let deserialized: NodeKind = serde_json::from_str(&json).unwrap();
109            assert_eq!(kind, deserialized);
110        }
111    }
112
113    #[test]
114    fn test_edge_kind_serde_all_variants() {
115        let variants = vec![
116            (EdgeKind::Uses, "\"uses\""),
117            (EdgeKind::Reads, "\"reads\""),
118            (EdgeKind::Writes, "\"writes\""),
119            (EdgeKind::Deploys, "\"deploys\""),
120            (EdgeKind::Contains, "\"contains\""),
121        ];
122        for (kind, expected_json) in variants {
123            let json = serde_json::to_string(&kind).unwrap();
124            assert_eq!(json, expected_json);
125            let deserialized: EdgeKind = serde_json::from_str(&json).unwrap();
126            assert_eq!(kind, deserialized);
127        }
128    }
129
130    #[test]
131    fn test_copy() {
132        let a = NodeKind::System;
133        let b = a;
134        assert_eq!(a, b);
135
136        let c = EdgeKind::Writes;
137        let d = c;
138        assert_eq!(c, d);
139    }
140
141    #[test]
142    fn test_node_kind_hash() {
143        use std::collections::HashSet;
144        let mut set = HashSet::new();
145        set.insert(NodeKind::Person);
146        set.insert(NodeKind::System);
147        set.insert(NodeKind::Person); // duplicate
148        assert_eq!(set.len(), 2);
149    }
150
151    #[test]
152    fn test_edge_kind_hash() {
153        use std::collections::HashSet;
154        let mut set = HashSet::new();
155        set.insert(EdgeKind::Uses);
156        set.insert(EdgeKind::Reads);
157        set.insert(EdgeKind::Uses); // duplicate
158        assert_eq!(set.len(), 2);
159    }
160
161    #[test]
162    fn test_node_kind_debug() {
163        let debug = format!("{:?}", NodeKind::Infrastructure);
164        assert!(debug.contains("Infrastructure"));
165    }
166
167    #[test]
168    fn test_edge_kind_debug() {
169        let debug = format!("{:?}", EdgeKind::Deploys);
170        assert!(debug.contains("Deploys"));
171    }
172
173    #[test]
174    fn test_node_kind_serde_invalid() {
175        let result: Result<NodeKind, _> = serde_json::from_str("\"invalid\"");
176        assert!(result.is_err());
177    }
178
179    #[test]
180    fn test_edge_kind_serde_invalid() {
181        let result: Result<EdgeKind, _> = serde_json::from_str("\"invalid\"");
182        assert!(result.is_err());
183    }
184}