toon_validate_core/
validator.rs1use crate::{
2 error::{Result, TqError},
3 value::{Table, Value},
4};
5use std::collections::{HashMap, HashSet};
6
7pub struct Validator;
8
9impl Validator {
10 pub fn validate(value: &Value) -> Result<()> {
11 match value {
12 Value::Object(obj) => Self::validate_object(obj),
13 Value::Table(table) => Self::validate_table(table),
14 Value::Array(arr) => {
15 for item in arr {
16 Self::validate(item)?;
17 }
18 Ok(())
19 }
20 _ => Ok(()),
21 }
22 }
23
24 fn validate_object(obj: &HashMap<String, Value>) -> Result<()> {
25 for value in obj.values() {
26 Self::validate(value)?;
27 }
28 Ok(())
29 }
30
31 fn validate_table(table: &Table) -> Result<()> {
32 if table.rows.len() != table.declared_rows {
34 return Err(TqError::TableRowMismatch {
35 name: table.name.clone(),
36 declared: table.declared_rows,
37 actual: table.rows.len(),
38 });
39 }
40
41 if !table.rows.is_empty() {
43 let mut schemas: Vec<HashSet<String>> = Vec::new();
44
45 for row in &table.rows {
46 let schema: HashSet<String> = row.keys().cloned().collect();
47 schemas.push(schema);
48 }
49
50 let first_schema = &schemas[0];
52 for (idx, schema) in schemas.iter().enumerate().skip(1) {
53 if schema != first_schema {
54 let missing: Vec<_> = first_schema.difference(schema).collect();
55 let extra: Vec<_> = schema.difference(first_schema).collect();
56
57 let mut message = format!("Row {} has different schema. ", idx + 1);
58 if !missing.is_empty() {
59 message.push_str(&format!("Missing fields: {:?}. ", missing));
60 }
61 if !extra.is_empty() {
62 message.push_str(&format!("Extra fields: {:?}. ", extra));
63 }
64
65 return Err(TqError::TableSchemaInconsistent {
66 name: table.name.clone(),
67 message,
68 });
69 }
70 }
71 }
72
73 for row in &table.rows {
75 for value in row.values() {
76 Self::validate(value)?;
77 }
78 }
79
80 Ok(())
81 }
82
83 pub fn check_structure(value: &Value) -> Vec<String> {
84 let mut issues = Vec::new();
85 Self::check_structure_recursive(value, "", &mut issues);
86 issues
87 }
88
89 fn check_structure_recursive(value: &Value, path: &str, issues: &mut Vec<String>) {
90 match value {
91 Value::Object(obj) => {
92 if obj.is_empty() {
93 let prefix = if path.is_empty() {
94 String::new()
95 } else {
96 format!("{}: ", path)
97 };
98 issues.push(format!("{}Empty object", prefix));
99 }
100 for (key, val) in obj {
101 let new_path = if path.is_empty() {
102 key.clone()
103 } else {
104 format!("{}.{}", path, key)
105 };
106 Self::check_structure_recursive(val, &new_path, issues);
107 }
108 }
109 Value::Array(arr) => {
110 if arr.is_empty() {
111 issues.push(format!("{}: Empty array", path));
112 }
113 for (idx, val) in arr.iter().enumerate() {
114 let new_path = format!("{}[{}]", path, idx);
115 Self::check_structure_recursive(val, &new_path, issues);
116 }
117 }
118 Value::Table(table) => {
119 if table.rows.is_empty() && table.declared_rows > 0 {
120 issues.push(format!("{}: Table declared with {} rows but is empty", path, table.declared_rows));
121 }
122 }
123 _ => {}
124 }
125 }
126}
127
128#[cfg(test)]
129mod tests {
130 use super::*;
131 use crate::value::Table;
132
133 #[test]
134 fn test_validate_primitives() {
135 assert!(Validator::validate(&Value::Null).is_ok());
136 assert!(Validator::validate(&Value::Bool(true)).is_ok());
137 assert!(Validator::validate(&Value::Number(42.0)).is_ok());
138 assert!(Validator::validate(&Value::String("test".to_string())).is_ok());
139 }
140
141 #[test]
142 fn test_validate_array() {
143 let arr = Value::Array(vec![
144 Value::Number(1.0),
145 Value::Number(2.0),
146 Value::String("test".to_string()),
147 ]);
148 assert!(Validator::validate(&arr).is_ok());
149 }
150
151 #[test]
152 fn test_validate_object() {
153 let mut obj = HashMap::new();
154 obj.insert("name".to_string(), Value::String("Alice".to_string()));
155 obj.insert("age".to_string(), Value::Number(30.0));
156 obj.insert("active".to_string(), Value::Bool(true));
157 let value = Value::Object(obj);
158
159 assert!(Validator::validate(&value).is_ok());
160 }
161
162 #[test]
163 fn test_validate_table_correct_rows() {
164 let mut row1 = HashMap::new();
165 row1.insert("id".to_string(), Value::Number(1.0));
166 row1.insert("name".to_string(), Value::String("Alice".to_string()));
167
168 let mut row2 = HashMap::new();
169 row2.insert("id".to_string(), Value::Number(2.0));
170 row2.insert("name".to_string(), Value::String("Bob".to_string()));
171
172 let table = Table {
173 name: "users".to_string(),
174 declared_rows: 2,
175 rows: vec![row1, row2],
176 };
177
178 assert!(Validator::validate(&Value::Table(table)).is_ok());
179 }
180
181 #[test]
182 fn test_validate_table_row_mismatch() {
183 let mut row1 = HashMap::new();
184 row1.insert("id".to_string(), Value::Number(1.0));
185
186 let table = Table {
187 name: "users".to_string(),
188 declared_rows: 3, rows: vec![row1],
190 };
191
192 let result = Validator::validate(&Value::Table(table));
193 assert!(result.is_err());
194 if let Err(e) = result {
195 let error_msg = e.to_string();
196 assert!(error_msg.contains("declared"));
197 assert!(error_msg.contains("3"));
198 assert!(error_msg.contains("1"));
199 }
200 }
201
202 #[test]
203 fn test_validate_table_schema_inconsistent() {
204 let mut row1 = HashMap::new();
205 row1.insert("id".to_string(), Value::Number(1.0));
206 row1.insert("name".to_string(), Value::String("Alice".to_string()));
207
208 let mut row2 = HashMap::new();
209 row2.insert("id".to_string(), Value::Number(2.0));
210 row2.insert("email".to_string(), Value::String("bob@example.com".to_string()));
211 let table = Table {
214 name: "users".to_string(),
215 declared_rows: 2,
216 rows: vec![row1, row2],
217 };
218
219 let result = Validator::validate(&Value::Table(table));
220 assert!(result.is_err());
221 if let Err(e) = result {
222 let error_msg = e.to_string();
223 assert!(error_msg.contains("different schema"));
224 }
225 }
226
227 #[test]
228 fn test_check_structure_empty() {
229 let obj = Value::Object(HashMap::new());
230 let issues = Validator::check_structure(&obj);
231 assert_eq!(issues.len(), 1);
232 assert!(issues[0].contains("Empty object"));
233
234 let arr = Value::Array(vec![]);
235 let issues = Validator::check_structure(&arr);
236 assert_eq!(issues.len(), 1);
237 assert!(issues[0].contains("Empty array"));
238 }
239
240 #[test]
241 fn test_check_structure_nested() {
242 let mut inner = HashMap::new();
243 inner.insert("empty".to_string(), Value::Array(vec![]));
244
245 let mut obj = HashMap::new();
246 obj.insert("nested".to_string(), Value::Object(inner));
247
248 let value = Value::Object(obj);
249 let issues = Validator::check_structure(&value);
250
251 assert_eq!(issues.len(), 1);
252 assert!(issues[0].contains("nested.empty"));
253 assert!(issues[0].contains("Empty array"));
254 }
255
256 #[test]
257 fn test_check_structure_table_empty() {
258 let table = Table {
259 name: "users".to_string(),
260 declared_rows: 5,
261 rows: vec![],
262 };
263
264 let mut obj = HashMap::new();
265 obj.insert("users".to_string(), Value::Table(table));
266
267 let value = Value::Object(obj);
268 let issues = Validator::check_structure(&value);
269
270 assert_eq!(issues.len(), 1);
271 assert!(issues[0].contains("declared with 5 rows but is empty"));
272 }
273}