Skip to main content

ito_domain/modules/
mod.rs

1//! Module domain models and repository.
2//!
3//! This module provides domain models for Ito modules and a repository
4//! for loading and querying module data.
5
6mod repository;
7
8pub use repository::ModuleRepository;
9
10use std::path::PathBuf;
11
12/// Full module with metadata loaded.
13#[derive(Debug, Clone)]
14pub struct Module {
15    /// Module identifier (e.g., "005")
16    pub id: String,
17    /// Module name (e.g., "dev-tooling")
18    pub name: String,
19    /// Optional description
20    pub description: Option<String>,
21    /// Path to the module directory
22    pub path: PathBuf,
23    /// Sub-modules belonging to this module; empty when none are defined.
24    pub sub_modules: Vec<SubModule>,
25}
26
27/// Lightweight module summary for listings.
28#[derive(Debug, Clone)]
29pub struct ModuleSummary {
30    /// Module identifier
31    pub id: String,
32    /// Module name
33    pub name: String,
34    /// Number of changes in this module
35    pub change_count: u32,
36    /// Sub-module summaries; empty when none are defined.
37    pub sub_modules: Vec<SubModuleSummary>,
38}
39
40/// A sub-module that groups changes within a parent module.
41///
42/// Sub-modules allow a module to be divided into named sections, each with
43/// their own change sequence. The canonical identifier is `NNN.SS` where
44/// `NNN` is the parent module number and `SS` is the sub-module number.
45#[derive(Debug, Clone)]
46pub struct SubModule {
47    /// Canonical sub-module identifier (e.g., "005.01")
48    pub id: String,
49    /// Parent module identifier (e.g., "005")
50    pub parent_module_id: String,
51    /// Sub-module number, zero-padded to 2 digits (e.g., "01")
52    pub sub_id: String,
53    /// Sub-module name (e.g., "core-api")
54    pub name: String,
55    /// Optional description
56    pub description: Option<String>,
57    /// Number of changes in this sub-module
58    pub change_count: u32,
59    /// Path to the sub-module directory
60    pub path: PathBuf,
61}
62
63/// Lightweight sub-module summary for listings.
64///
65/// Included in [`ModuleSummary`] when sub-modules are present.
66#[derive(Debug, Clone)]
67pub struct SubModuleSummary {
68    /// Canonical sub-module identifier (e.g., "005.01")
69    pub id: String,
70    /// Sub-module name
71    pub name: String,
72    /// Number of changes in this sub-module
73    pub change_count: u32,
74}
75
76#[cfg(test)]
77mod tests {
78    use super::*;
79
80    #[test]
81    fn test_module_creation() {
82        let module = Module {
83            id: "005".to_string(),
84            name: "dev-tooling".to_string(),
85            description: Some("Development tooling".to_string()),
86            path: PathBuf::from("/test"),
87            sub_modules: Vec::new(),
88        };
89
90        assert_eq!(module.id, "005");
91        assert_eq!(module.name, "dev-tooling");
92        assert!(module.sub_modules.is_empty());
93    }
94
95    #[test]
96    fn test_module_summary() {
97        let summary = ModuleSummary {
98            id: "005".to_string(),
99            name: "dev-tooling".to_string(),
100            change_count: 3,
101            sub_modules: Vec::new(),
102        };
103
104        assert_eq!(summary.change_count, 3);
105        assert!(summary.sub_modules.is_empty());
106    }
107
108    #[test]
109    fn test_sub_module_creation() {
110        let sub = SubModule {
111            id: "005.01".to_string(),
112            parent_module_id: "005".to_string(),
113            sub_id: "01".to_string(),
114            name: "core-api".to_string(),
115            description: Some("Core API sub-module".to_string()),
116            change_count: 2,
117            path: PathBuf::from("/test/005.01_core-api"),
118        };
119
120        assert_eq!(sub.id, "005.01");
121        assert_eq!(sub.parent_module_id, "005");
122        assert_eq!(sub.sub_id, "01");
123        assert_eq!(sub.name, "core-api");
124        assert_eq!(sub.change_count, 2);
125    }
126
127    #[test]
128    fn test_sub_module_summary_creation() {
129        let summary = SubModuleSummary {
130            id: "005.01".to_string(),
131            name: "core-api".to_string(),
132            change_count: 2,
133        };
134
135        assert_eq!(summary.id, "005.01");
136        assert_eq!(summary.name, "core-api");
137        assert_eq!(summary.change_count, 2);
138    }
139
140    #[test]
141    fn test_module_with_sub_modules() {
142        let sub = SubModule {
143            id: "005.01".to_string(),
144            parent_module_id: "005".to_string(),
145            sub_id: "01".to_string(),
146            name: "core-api".to_string(),
147            description: None,
148            change_count: 1,
149            path: PathBuf::from("/test/005.01_core-api"),
150        };
151
152        let module = Module {
153            id: "005".to_string(),
154            name: "dev-tooling".to_string(),
155            description: None,
156            path: PathBuf::from("/test"),
157            sub_modules: vec![sub],
158        };
159
160        assert_eq!(module.sub_modules.len(), 1);
161        assert_eq!(module.sub_modules[0].id, "005.01");
162    }
163
164    #[test]
165    fn test_module_summary_with_sub_modules() {
166        let sub_summary = SubModuleSummary {
167            id: "005.01".to_string(),
168            name: "core-api".to_string(),
169            change_count: 2,
170        };
171
172        let summary = ModuleSummary {
173            id: "005".to_string(),
174            name: "dev-tooling".to_string(),
175            change_count: 5,
176            sub_modules: vec![sub_summary],
177        };
178
179        assert_eq!(summary.sub_modules.len(), 1);
180        assert_eq!(summary.sub_modules[0].change_count, 2);
181    }
182}