dampen_cli/commands/check/
model.rs1use serde::{Deserialize, Serialize};
2use std::collections::HashSet;
3use std::fs;
4use std::path::Path;
5
6#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
8pub struct ModelField {
9 pub name: String,
11
12 #[serde(default)]
14 pub type_name: String,
15
16 #[serde(default)]
18 pub is_nested: bool,
19
20 #[serde(default)]
22 pub children: Vec<ModelField>,
23}
24
25#[derive(Debug, Clone, Default)]
27pub struct ModelInfo {
28 fields: HashSet<ModelField>,
29}
30
31impl ModelInfo {
32 pub fn new() -> Self {
34 Self {
35 fields: HashSet::new(),
36 }
37 }
38
39 pub fn load_from_json(path: &Path) -> Result<Self, Box<dyn std::error::Error>> {
71 let content = fs::read_to_string(path)?;
72 let fields: Vec<ModelField> = serde_json::from_str(&content)?;
73
74 let mut model = Self::new();
75 for field in fields {
76 model.add_field(field);
77 }
78
79 Ok(model)
80 }
81
82 pub fn contains_field(&self, path: &[&str]) -> bool {
92 if path.is_empty() {
93 return false;
94 }
95
96 let top_level_name = path[0];
98 let top_level_field = self.fields.iter().find(|f| f.name == top_level_name);
99
100 match top_level_field {
101 None => false,
102 Some(field) => {
103 if path.len() == 1 {
104 true
106 } else {
107 Self::contains_nested_field(field, &path[1..])
109 }
110 }
111 }
112 }
113
114 fn contains_nested_field(field: &ModelField, path: &[&str]) -> bool {
116 if path.is_empty() {
117 return true;
118 }
119
120 if !field.is_nested {
122 return false;
123 }
124
125 let next_name = path[0];
127 let next_field = field.children.iter().find(|f| f.name == next_name);
128
129 match next_field {
130 None => false,
131 Some(child) => {
132 if path.len() == 1 {
133 true
134 } else {
135 Self::contains_nested_field(child, &path[1..])
136 }
137 }
138 }
139 }
140
141 pub fn top_level_fields(&self) -> Vec<&str> {
147 self.fields.iter().map(|f| f.name.as_str()).collect()
148 }
149
150 pub fn all_field_paths(&self) -> Vec<String> {
156 let mut paths = Vec::new();
157
158 for field in &self.fields {
159 paths.push(field.name.clone());
160 Self::collect_nested_paths(field, &field.name, &mut paths);
161 }
162
163 paths
164 }
165
166 fn collect_nested_paths(field: &ModelField, prefix: &str, paths: &mut Vec<String>) {
168 if field.is_nested {
169 for child in &field.children {
170 let path = format!("{}.{}", prefix, child.name);
171 paths.push(path.clone());
172 Self::collect_nested_paths(child, &path, paths);
173 }
174 }
175 }
176
177 pub fn add_field(&mut self, field: ModelField) {
179 self.fields.insert(field);
180 }
181
182 pub fn len(&self) -> usize {
184 self.fields.len()
185 }
186
187 pub fn is_empty(&self) -> bool {
189 self.fields.is_empty()
190 }
191}
192
193#[cfg(test)]
194mod tests {
195 use super::*;
196
197 #[test]
198 fn test_empty_model() {
199 let model = ModelInfo::new();
200 assert!(model.is_empty());
201 assert_eq!(model.len(), 0);
202 assert!(!model.contains_field(&["count"]));
203 }
204
205 #[test]
206 fn test_add_simple_field() {
207 let mut model = ModelInfo::new();
208
209 let field = ModelField {
210 name: "count".to_string(),
211 type_name: "i32".to_string(),
212 is_nested: false,
213 children: vec![],
214 };
215
216 model.add_field(field);
217
218 assert!(!model.is_empty());
219 assert_eq!(model.len(), 1);
220 assert!(model.contains_field(&["count"]));
221 assert!(!model.contains_field(&["unknown"]));
222 }
223
224 #[test]
225 fn test_nested_field() {
226 let mut model = ModelInfo::new();
227
228 let field = ModelField {
229 name: "user".to_string(),
230 type_name: "User".to_string(),
231 is_nested: true,
232 children: vec![ModelField {
233 name: "name".to_string(),
234 type_name: "String".to_string(),
235 is_nested: false,
236 children: vec![],
237 }],
238 };
239
240 model.add_field(field);
241
242 assert!(model.contains_field(&["user"]));
243 assert!(model.contains_field(&["user", "name"]));
244 assert!(!model.contains_field(&["user", "email"]));
245 }
246
247 #[test]
248 fn test_top_level_fields() {
249 let mut model = ModelInfo::new();
250
251 model.add_field(ModelField {
252 name: "count".to_string(),
253 type_name: "i32".to_string(),
254 is_nested: false,
255 children: vec![],
256 });
257
258 model.add_field(ModelField {
259 name: "enabled".to_string(),
260 type_name: "bool".to_string(),
261 is_nested: false,
262 children: vec![],
263 });
264
265 let fields = model.top_level_fields();
266 assert_eq!(fields.len(), 2);
267 assert!(fields.contains(&"count"));
268 assert!(fields.contains(&"enabled"));
269 }
270
271 #[test]
272 fn test_all_field_paths() {
273 let mut model = ModelInfo::new();
274
275 model.add_field(ModelField {
276 name: "count".to_string(),
277 type_name: "i32".to_string(),
278 is_nested: false,
279 children: vec![],
280 });
281
282 model.add_field(ModelField {
283 name: "user".to_string(),
284 type_name: "User".to_string(),
285 is_nested: true,
286 children: vec![ModelField {
287 name: "name".to_string(),
288 type_name: "String".to_string(),
289 is_nested: false,
290 children: vec![],
291 }],
292 });
293
294 let paths = model.all_field_paths();
295 assert!(paths.contains(&"count".to_string()));
296 assert!(paths.contains(&"user".to_string()));
297 assert!(paths.contains(&"user.name".to_string()));
298 }
299}