1use std::{collections::HashSet, error::Error, fmt};
2
3use serde::{Deserialize, Serialize};
4
5#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
7#[serde(try_from = "u8", into = "u8")]
8pub enum LayoutDirection {
9 Left,
11 #[default]
13 Right,
14 Side,
16 Down,
18}
19impl TryFrom<u8> for LayoutDirection {
20 type Error = String;
21 fn try_from(value: u8) -> Result<Self, Self::Error> {
22 match value {
23 0 => Ok(Self::Left),
24 1 => Ok(Self::Right),
25 2 => Ok(Self::Side),
26 3 => Ok(Self::Down),
27 _ => Err(format!("Invalid layout direction: {value}")),
28 }
29 }
30}
31impl From<LayoutDirection> for u8 {
32 fn from(value: LayoutDirection) -> Self {
33 match value {
34 LayoutDirection::Left => 0,
35 LayoutDirection::Right => 1,
36 LayoutDirection::Side => 2,
37 LayoutDirection::Down => 3,
38 }
39 }
40}
41
42#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
44#[serde(try_from = "u8", into = "u8")]
45pub enum NodeDirection {
46 Left,
48 Right,
50}
51impl TryFrom<u8> for NodeDirection {
52 type Error = String;
53 fn try_from(value: u8) -> Result<Self, Self::Error> {
54 match value {
55 0 => Ok(Self::Left),
56 1 => Ok(Self::Right),
57 _ => Err(format!("Invalid node direction: {value}")),
58 }
59 }
60}
61impl From<NodeDirection> for u8 {
62 fn from(value: NodeDirection) -> Self {
63 match value {
64 NodeDirection::Left => 0,
65 NodeDirection::Right => 1,
66 }
67 }
68}
69
70#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
72#[serde(rename_all = "camelCase", deny_unknown_fields)]
73pub struct NodeStyle {
74 #[serde(default, skip_serializing_if = "Option::is_none")]
76 pub font_size: Option<String>,
77 #[serde(default, skip_serializing_if = "Option::is_none")]
79 pub font_family: Option<String>,
80 #[serde(default, skip_serializing_if = "Option::is_none")]
82 pub color: Option<String>,
83 #[serde(default, skip_serializing_if = "Option::is_none")]
85 pub background: Option<String>,
86 #[serde(default, skip_serializing_if = "Option::is_none")]
88 pub font_weight: Option<String>,
89 #[serde(default, skip_serializing_if = "Option::is_none")]
91 pub width: Option<String>,
92 #[serde(default, skip_serializing_if = "Option::is_none")]
94 pub border: Option<String>,
95 #[serde(default, skip_serializing_if = "Option::is_none")]
97 pub text_decoration: Option<String>,
98}
99
100fn present_json<'de, D: serde::Deserializer<'de>>(
101 deserializer: D,
102) -> Result<Option<serde_json::Value>, D::Error> {
103 serde_json::Value::deserialize(deserializer).map(Some)
104}
105
106fn expanded_default() -> bool {
107 true
108}
109
110#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
112#[serde(rename_all = "camelCase", deny_unknown_fields)]
113pub struct Node {
114 pub id: String,
116 pub topic: String,
118 #[serde(default, skip_serializing_if = "Vec::is_empty")]
120 pub children: Vec<Node>,
121 #[serde(default = "expanded_default")]
123 pub expanded: bool,
124 #[serde(default, skip_serializing_if = "Option::is_none")]
126 pub direction: Option<NodeDirection>,
127 #[serde(default, skip_serializing_if = "Option::is_none")]
129 pub style: Option<NodeStyle>,
130 #[serde(default, skip_serializing_if = "Option::is_none")]
132 pub branch_color: Option<String>,
133 #[serde(default, skip_serializing_if = "Option::is_none")]
135 pub note: Option<String>,
136 #[serde(
138 default,
139 deserialize_with = "present_json",
140 skip_serializing_if = "Option::is_none"
141 )]
142 pub metadata: Option<serde_json::Value>,
143}
144impl Node {
145 pub fn new(id: impl Into<String>, topic: impl Into<String>) -> Self {
147 Self {
148 id: id.into(),
149 topic: topic.into(),
150 children: Vec::new(),
151 expanded: true,
152 direction: None,
153 style: None,
154 branch_color: None,
155 note: None,
156 metadata: None,
157 }
158 }
159 pub fn with_children(mut self, children: Vec<Node>) -> Self {
161 self.children = children;
162 self
163 }
164 pub fn find(&self, id: &str) -> Option<&Node> {
166 let mut pending = vec![self];
167 while let Some(node) = pending.pop() {
168 if node.id == id {
169 return Some(node);
170 }
171 pending.extend(node.children.iter().rev());
172 }
173 None
174 }
175 pub fn validate(&self) -> Result<(), MindMapError> {
177 let mut pending = vec![(self, 0usize)];
178 let mut ids = HashSet::new();
179 while let Some((node, depth)) = pending.pop() {
180 if depth > 128 {
181 return Err(MindMapError::TreeTooDeep);
182 }
183 if node.id.trim().is_empty() {
184 return Err(MindMapError::EmptyNodeId);
185 }
186 if !ids.insert(node.id.as_str()) {
187 return Err(MindMapError::DuplicateNodeId(node.id.clone()));
188 }
189 pending.extend(node.children.iter().map(|child| (child, depth + 1)));
190 }
191 Ok(())
192 }
193 pub(crate) fn find_mut(&mut self, id: &str) -> Option<&mut Node> {
194 if self.id == id {
195 return Some(self);
196 }
197 self.children
198 .iter_mut()
199 .find_map(|child| child.find_mut(id))
200 }
201}
202
203#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
205#[serde(rename_all = "camelCase", deny_unknown_fields)]
206pub struct MindMapData {
207 pub node_data: Node,
209 #[serde(default)]
211 pub direction: LayoutDirection,
212 #[serde(default)]
214 pub compact: bool,
215 #[serde(
217 default,
218 deserialize_with = "present_json",
219 skip_serializing_if = "Option::is_none"
220 )]
221 pub meta: Option<serde_json::Value>,
222}
223impl MindMapData {
224 pub fn new(root: Node) -> Self {
226 Self {
227 node_data: root,
228 direction: LayoutDirection::Right,
229 compact: false,
230 meta: None,
231 }
232 }
233 pub fn validate(&self) -> Result<(), MindMapError> {
235 self.node_data.validate()
236 }
237
238 pub fn from_json(json: &str) -> Result<Self, MindMapError> {
240 let mut deserializer = serde_json::Deserializer::from_str(json);
241 let document: Self =
242 serde_path_to_error::deserialize(&mut deserializer).map_err(|error| {
243 MindMapError::InvalidData {
244 path: error.path().to_string(),
245 message: error.inner().to_string(),
246 }
247 })?;
248 deserializer
249 .end()
250 .map_err(|error| MindMapError::InvalidData {
251 path: String::new(),
252 message: error.to_string(),
253 })?;
254 document.validate()?;
255 Ok(document)
256 }
257 pub fn to_json(&self) -> Result<String, MindMapError> {
259 self.validate()?;
260 serde_json::to_string_pretty(self).map_err(|error| MindMapError::InvalidData {
261 path: String::new(),
262 message: error.to_string(),
263 })
264 }
265}
266
267#[derive(Clone, Debug, PartialEq, Eq)]
269#[non_exhaustive]
270pub enum MindMapError {
271 EmptyNodeId,
273 DuplicateNodeId(String),
275 TreeTooDeep,
277 InvalidData {
279 path: String,
281 message: String,
283 },
284 NodeNotFound(String),
286 RootOperation,
288 AncestorMove,
290 ReadOnly,
292 Rejected,
294 StaleOperation,
296 NoHistory,
298 InvalidOperation(String),
300 Measurement(String),
302 InvalidGeometry,
304}
305impl fmt::Display for MindMapError {
306 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
307 match self {
308 Self::EmptyNodeId => f.write_str("Node IDs must not be empty"),
309 Self::DuplicateNodeId(id) => write!(f, "Duplicate node ID: {id}"),
310 Self::TreeTooDeep => f.write_str("At most 128 nested levels are supported"),
311 Self::InvalidData { path, message } => {
312 write!(f, "Invalid document at {path}: {message}")
313 }
314 Self::NodeNotFound(id) => write!(f, "Node not found: {id}"),
315 Self::RootOperation => f.write_str("The root cannot be removed or relocated"),
316 Self::AncestorMove => f.write_str("Cannot move a node into its own subtree"),
317 Self::ReadOnly => f.write_str("The document is read-only"),
318 Self::Rejected => f.write_str("The operation was rejected by the before hook"),
319 Self::StaleOperation => f.write_str("The document changed during the operation"),
320 Self::NoHistory => f.write_str("No history entry is available"),
321 Self::InvalidOperation(message) => write!(f, "Invalid operation: {message}"),
322 Self::Measurement(message) => write!(f, "Cannot measure the mind map: {message}"),
323 Self::InvalidGeometry => f.write_str("The renderer returned invalid geometry"),
324 }
325 }
326}
327impl Error for MindMapError {}