1use serde::{Deserialize, Serialize};
4
5use crate::container::Container;
6use crate::id::NodeId;
7use crate::layout::TierLayout;
8use crate::node::Node;
9
10#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
15#[serde(rename_all = "snake_case")]
16pub struct Tier {
17 id: NodeId,
18 #[serde(default, skip_serializing_if = "Option::is_none")]
19 label: Option<String>,
20 #[serde(default, skip_serializing_if = "is_auto")]
21 layout: TierLayout,
22 #[serde(default, skip_serializing_if = "Vec::is_empty")]
23 nodes: Vec<Node>,
24 #[serde(default, skip_serializing_if = "Option::is_none")]
25 container: Option<Container>,
26}
27
28fn is_auto(layout: &TierLayout) -> bool {
29 *layout == TierLayout::Auto
30}
31
32impl Tier {
33 pub fn new(id: NodeId, nodes: Vec<Node>) -> Self {
35 Self {
36 id,
37 label: None,
38 layout: TierLayout::default(),
39 nodes,
40 container: None,
41 }
42 }
43
44 pub fn with_container(id: NodeId, container: Container) -> Self {
46 Self {
47 id,
48 label: None,
49 layout: TierLayout::default(),
50 nodes: Vec::new(),
51 container: Some(container),
52 }
53 }
54
55 pub fn id(&self) -> &NodeId {
57 &self.id
58 }
59
60 pub fn label(&self) -> Option<&str> {
62 self.label.as_deref()
63 }
64
65 pub fn set_label(&mut self, label: &str) {
67 self.label = Some(label.to_owned());
68 }
69
70 pub fn layout(&self) -> &TierLayout {
72 &self.layout
73 }
74
75 pub fn set_layout(&mut self, layout: TierLayout) {
77 self.layout = layout;
78 }
79
80 pub fn nodes(&self) -> &[Node] {
82 &self.nodes
83 }
84
85 pub fn container(&self) -> Option<&Container> {
87 self.container.as_ref()
88 }
89
90 pub fn is_empty(&self) -> bool {
92 self.nodes.is_empty() && self.container.is_none()
93 }
94}
95
96#[cfg(test)]
97mod tests {
98 use super::*;
99 use crate::color::Color;
100 use crate::kind::NodeKind;
101 use crate::layer::Layer;
102
103 #[test]
104 fn test_new_with_nodes() {
105 let node = Node::builder()
106 .id(NodeId::new("app").unwrap())
107 .kind(NodeKind::System)
108 .color(Color::Blue)
109 .icon("◇")
110 .title("App")
111 .description("The app")
112 .build()
113 .unwrap();
114
115 let tier = Tier::new(NodeId::new("clients").unwrap(), vec![node]);
116 assert_eq!(tier.id().as_str(), "clients");
117 assert_eq!(tier.nodes().len(), 1);
118 assert!(!tier.is_empty());
119 }
120
121 #[test]
122 fn test_empty_tier() {
123 let tier = Tier::new(NodeId::new("empty").unwrap(), vec![]);
124 assert!(tier.is_empty());
125 }
126
127 #[test]
128 fn test_with_container() {
129 use crate::container::{Container, ContainerBorder};
130
131 let container = Container::new(
132 "server",
133 ContainerBorder::Solid,
134 Color::Green,
135 vec![Layer::Tier(Tier::new(
136 NodeId::new("inner").unwrap(),
137 vec![
138 Node::builder()
139 .id(NodeId::new("api").unwrap())
140 .kind(NodeKind::System)
141 .color(Color::Green)
142 .icon("x")
143 .title("API")
144 .description("desc")
145 .build()
146 .unwrap(),
147 ],
148 ))],
149 );
150 let tier = Tier::with_container(NodeId::new("server").unwrap(), container);
151 assert!(!tier.is_empty());
152 assert!(tier.container().is_some());
153 assert!(tier.nodes().is_empty());
154 }
155
156 #[test]
157 fn test_label_and_set_label() {
158 let mut tier = Tier::new(NodeId::new("t").unwrap(), vec![]);
159 assert!(tier.label().is_none());
160 tier.set_label("My Tier");
161 assert_eq!(tier.label(), Some("My Tier"));
162 }
163
164 #[test]
165 fn test_layout_and_set_layout() {
166 use crate::layout::TierLayout;
167
168 let mut tier = Tier::new(NodeId::new("t").unwrap(), vec![]);
169 assert_eq!(*tier.layout(), TierLayout::Auto);
170 tier.set_layout(TierLayout::Single);
171 assert_eq!(*tier.layout(), TierLayout::Single);
172 tier.set_layout(TierLayout::Grid { columns: 3 });
173 assert_eq!(*tier.layout(), TierLayout::Grid { columns: 3 });
174 }
175
176 #[test]
177 fn test_tier_serde_round_trip() {
178 let node = Node::builder()
179 .id(NodeId::new("app").unwrap())
180 .kind(NodeKind::System)
181 .color(Color::Blue)
182 .icon("x")
183 .title("App")
184 .description("desc")
185 .build()
186 .unwrap();
187 let tier = Tier::new(NodeId::new("main").unwrap(), vec![node]);
188 let json = serde_json::to_string(&tier).unwrap();
189 let deserialized: Tier = serde_json::from_str(&json).unwrap();
190 assert_eq!(tier, deserialized);
191 }
192}