elasticube_core/cube/
hierarchy.rs1use serde::{Deserialize, Serialize};
4
5#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
10pub struct Hierarchy {
11 name: String,
13
14 levels: Vec<String>,
17
18 description: Option<String>,
20}
21
22impl Hierarchy {
23 pub fn new(name: impl Into<String>, levels: Vec<String>) -> Self {
39 Self {
40 name: name.into(),
41 levels,
42 description: None,
43 }
44 }
45
46 pub fn with_config(
48 name: impl Into<String>,
49 levels: Vec<String>,
50 description: Option<String>,
51 ) -> Self {
52 Self {
53 name: name.into(),
54 levels,
55 description,
56 }
57 }
58
59 pub fn name(&self) -> &str {
61 &self.name
62 }
63
64 pub fn levels(&self) -> &[String] {
66 &self.levels
67 }
68
69 pub fn description(&self) -> Option<&str> {
71 self.description.as_deref()
72 }
73
74 pub fn depth(&self) -> usize {
76 self.levels.len()
77 }
78
79 pub fn level_at(&self, index: usize) -> Option<&str> {
81 self.levels.get(index).map(|s| s.as_str())
82 }
83
84 pub fn top_level(&self) -> Option<&str> {
86 self.levels.first().map(|s| s.as_str())
87 }
88
89 pub fn bottom_level(&self) -> Option<&str> {
91 self.levels.last().map(|s| s.as_str())
92 }
93
94 pub fn parent_of(&self, level: &str) -> Option<&str> {
96 self.levels
97 .iter()
98 .position(|l| l == level)
99 .and_then(|idx| {
100 if idx > 0 {
101 self.levels.get(idx - 1).map(|s| s.as_str())
102 } else {
103 None
104 }
105 })
106 }
107
108 pub fn child_of(&self, level: &str) -> Option<&str> {
110 self.levels
111 .iter()
112 .position(|l| l == level)
113 .and_then(|idx| self.levels.get(idx + 1).map(|s| s.as_str()))
114 }
115
116 pub fn contains_level(&self, level: &str) -> bool {
118 self.levels.iter().any(|l| l == level)
119 }
120
121 pub fn ancestors_of(&self, level: &str) -> Vec<&str> {
123 if let Some(idx) = self.levels.iter().position(|l| l == level) {
124 self.levels[..idx].iter().map(|s| s.as_str()).collect()
125 } else {
126 vec![]
127 }
128 }
129
130 pub fn descendants_of(&self, level: &str) -> Vec<&str> {
132 if let Some(idx) = self.levels.iter().position(|l| l == level) {
133 self.levels[idx + 1..]
134 .iter()
135 .map(|s| s.as_str())
136 .collect()
137 } else {
138 vec![]
139 }
140 }
141
142 pub fn set_description(&mut self, description: impl Into<String>) {
144 self.description = Some(description.into());
145 }
146
147 pub fn with_description(mut self, description: impl Into<String>) -> Self {
149 self.description = Some(description.into());
150 self
151 }
152
153 pub fn validate(&self) -> Result<(), String> {
155 if self.levels.is_empty() {
156 return Err("Hierarchy must have at least one level".to_string());
157 }
158
159 let mut seen = std::collections::HashSet::new();
161 for level in &self.levels {
162 if !seen.insert(level) {
163 return Err(format!("Duplicate level '{}' in hierarchy", level));
164 }
165 }
166
167 Ok(())
168 }
169}
170
171#[cfg(test)]
172mod tests {
173 use super::*;
174
175 #[test]
176 fn test_hierarchy_creation() {
177 let hierarchy = Hierarchy::new(
178 "time",
179 vec![
180 "year".to_string(),
181 "quarter".to_string(),
182 "month".to_string(),
183 "day".to_string(),
184 ],
185 );
186
187 assert_eq!(hierarchy.name(), "time");
188 assert_eq!(hierarchy.depth(), 4);
189 assert_eq!(hierarchy.top_level(), Some("year"));
190 assert_eq!(hierarchy.bottom_level(), Some("day"));
191 }
192
193 #[test]
194 fn test_hierarchy_navigation() {
195 let hierarchy = Hierarchy::new(
196 "geography",
197 vec![
198 "country".to_string(),
199 "state".to_string(),
200 "city".to_string(),
201 ],
202 );
203
204 assert_eq!(hierarchy.parent_of("state"), Some("country"));
205 assert_eq!(hierarchy.parent_of("country"), None);
206 assert_eq!(hierarchy.child_of("country"), Some("state"));
207 assert_eq!(hierarchy.child_of("city"), None);
208 }
209
210 #[test]
211 fn test_hierarchy_ancestors_descendants() {
212 let hierarchy = Hierarchy::new(
213 "time",
214 vec![
215 "year".to_string(),
216 "quarter".to_string(),
217 "month".to_string(),
218 "day".to_string(),
219 ],
220 );
221
222 assert_eq!(hierarchy.ancestors_of("month"), vec!["year", "quarter"]);
223 assert_eq!(hierarchy.descendants_of("quarter"), vec!["month", "day"]);
224 assert_eq!(hierarchy.ancestors_of("year"), Vec::<&str>::new());
225 assert_eq!(hierarchy.descendants_of("day"), Vec::<&str>::new());
226 }
227
228 #[test]
229 fn test_hierarchy_validation() {
230 let valid = Hierarchy::new(
231 "test",
232 vec!["level1".to_string(), "level2".to_string()],
233 );
234 assert!(valid.validate().is_ok());
235
236 let empty = Hierarchy::new("test", vec![]);
237 assert!(empty.validate().is_err());
238
239 let duplicate = Hierarchy::new(
240 "test",
241 vec!["level1".to_string(), "level1".to_string()],
242 );
243 assert!(duplicate.validate().is_err());
244 }
245}