Skip to main content

mindtask/model/
concept.rs

1//! The concept (category/topic) type.
2
3use serde::{Deserialize, Serialize};
4
5use super::id::ConceptId;
6
7/// A named category that tasks can be grouped under.
8///
9/// Concepts form a tree via the optional [`parent`](Self::parent) field.
10/// A concept with no parent is a root node.
11#[derive(Debug, Clone, Serialize, Deserialize)]
12pub struct Concept {
13    /// Unique identifier.
14    pub id: ConceptId,
15    /// Human-readable name.
16    pub name: String,
17    /// Optional longer description.
18    #[serde(default, skip_serializing_if = "Option::is_none")]
19    pub description: Option<String>,
20    /// Parent concept, or `None` for root concepts.
21    #[serde(default, skip_serializing_if = "Option::is_none")]
22    pub parent: Option<ConceptId>,
23}
24
25#[cfg(test)]
26mod tests {
27    use super::*;
28
29    #[test]
30    fn concept_serde_roundtrip() {
31        let concept = Concept {
32            id: ConceptId(1),
33            name: "Backend".to_string(),
34            description: Some("Server-side code".to_string()),
35            parent: None,
36        };
37        let json = serde_json::to_string_pretty(&concept).unwrap();
38        let parsed: Concept = serde_json::from_str(&json).unwrap();
39        assert_eq!(parsed.id, concept.id);
40        assert_eq!(parsed.name, concept.name);
41        assert_eq!(parsed.description, concept.description);
42        assert_eq!(parsed.parent, concept.parent);
43    }
44
45    #[test]
46    fn concept_skips_none_fields() {
47        let concept = Concept {
48            id: ConceptId(1),
49            name: "Root".to_string(),
50            description: None,
51            parent: None,
52        };
53        let json = serde_json::to_string(&concept).unwrap();
54        assert!(!json.contains("description"));
55        assert!(!json.contains("parent"));
56    }
57
58    #[test]
59    fn concept_with_parent() {
60        let concept = Concept {
61            id: ConceptId(2),
62            name: "Child".to_string(),
63            description: None,
64            parent: Some(ConceptId(1)),
65        };
66        let json = serde_json::to_string(&concept).unwrap();
67        assert!(json.contains("\"parent\":1"));
68        let parsed: Concept = serde_json::from_str(&json).unwrap();
69        assert_eq!(parsed.parent, Some(ConceptId(1)));
70    }
71}