1use std::collections::HashMap;
2
3use crate::{
4 ast::{Constraint, FieldRule, FieldType, Value},
5 parser::Parser,
6 token::tokenize,
7};
8use regex::Regex;
9
10pub fn validate_field(value: &mut Value, rule: &FieldRule) -> Result<(), String> {
14 if let Value::Object(obj) = value {
16 if !obj.contains_key(&rule.field) {
17 if let Some(d) = &rule.default {
18 obj.insert(rule.field.clone(), d.clone());
19 }
20 }
21 }
22
23 let val_opt = match value {
25 Value::Object(obj) => obj.get_mut(&rule.field),
26 _ => Some(value),
27 };
28
29 if val_opt.is_none() {
30 if rule.required {
31 return Err(format!("Missing required field {}", rule.field));
32 } else {
33 return Ok(());
34 }
35 }
36
37 let val = val_opt.unwrap();
38 if !rule.required {
39 if let Value::String(s) = val {
40 if s.is_empty() {
41 return Ok(());
42 }
43 }
44 }
45 if let Some(types) = &rule.union_types {
47 let mut ok = false;
48 for t in types {
49 if validate_type(val, t).is_ok() {
50 ok = true;
51 break;
52 }
53 }
54 if !ok {
55 return Err(format!(
56 "{} value {:?} does not match union types {:?}",
57 rule.field, val, types
58 ));
59 }
60 } else {
61 validate_type(val, &rule.field_type)
63 .map_err(|e| format!("{} value {:?}: {}", rule.field, val, e))?;
64 }
65
66 if let Some(enum_vals) = &rule.enum_values {
68 if !enum_vals.contains(val) {
69 return Err(format!(
70 "{} value {:?} not in enum {:?}",
71 rule.field, val, enum_vals
72 ));
73 }
74 }
75
76 if let Some(c) = &rule.constraints {
78 for con in &c.items {
79 match con {
80 Constraint::Range {
81 min,
82 max,
83 min_inclusive,
84 max_inclusive,
85 } => {
86 match val {
87 Value::Int(i) => {
88 let n = *i as f64;
89 let min_v = match min {
91 Value::Int(mi) => *mi as f64,
92 Value::Float(mf) => mf.ceil(), _ => {
94 return Err(format!(
95 "Invalid min value type for {}",
96 rule.field
97 ));
98 }
99 };
100 let max_v = match max {
101 Value::Int(mi) => *mi as f64,
102 Value::Float(mf) => mf.floor(), _ => {
104 return Err(format!(
105 "Invalid max value type for {}",
106 rule.field
107 ));
108 }
109 };
110 let min_ok = if *min_inclusive {
113 n >= min_v
114 } else {
115 n > min_v
116 };
117 let max_ok = if *max_inclusive {
118 n <= max_v
119 } else {
120 n < max_v
121 };
122
123 if !min_ok || !max_ok {
124 return Err(format!(
125 "{} value {} out of range [{}, {}]",
126 rule.field, i, min_v, max_v
127 ));
128 }
129 }
130 Value::Float(f) => {
131 let n = *f;
132 let min_v = match min {
133 Value::Int(mi) => *mi as f64,
134 Value::Float(mf) => *mf,
135 _ => {
136 return Err(format!(
137 "Invalid min value type in range for {}",
138 rule.field
139 ));
140 }
141 };
142 let max_v = match max {
143 Value::Int(mi) => *mi as f64,
144 Value::Float(mf) => *mf,
145 _ => {
146 return Err(format!(
147 "Invalid max value type in range for {}",
148 rule.field
149 ));
150 }
151 };
152 let min_ok = if *min_inclusive {
153 n >= min_v
154 } else {
155 n > min_v
156 };
157 let max_ok = if *max_inclusive {
158 n <= max_v
159 } else {
160 n < max_v
161 };
162 if !min_ok || !max_ok {
163 return Err(format!(
164 "{} value {:?} out of range [{:?}, {:?}]",
165 rule.field, val, min, max
166 ));
167 }
168 }
169 Value::String(s) => {
170 let n = s.len();
171 let min_v = match min {
173 Value::Int(mi) => *mi as usize,
174 Value::String(s) => s
175 .parse::<usize>()
176 .map_err(|_| format!("Failed to parse '{}' as usize", s))?,
177 _ => {
178 return Err(format!(
179 "Invalid min value type in range for {}",
180 rule.field
181 ));
182 }
183 };
184 let max_v = match max {
185 Value::Int(mi) => *mi as usize,
186 Value::String(s) => s
187 .parse::<usize>()
188 .map_err(|_| format!("Failed to parse '{}' as usize", s))?,
189 _ => {
190 return Err(format!(
191 "Invalid max value type in range for {}",
192 rule.field
193 ));
194 }
195 };
196 let min_ok = if *min_inclusive {
197 n >= min_v
198 } else {
199 n > min_v
200 };
201 let max_ok = if *max_inclusive {
202 n <= max_v
203 } else {
204 n < max_v
205 };
206 if !min_ok || !max_ok {
207 return Err(format!(
208 "{} length {} out of range [{:?}, {:?}]",
209 rule.field, n, min, max
210 ));
211 }
212 }
213 _ => {
214 return Err(format!(
215 "{} cannot apply range constraint to {:?}",
216 rule.field, val
217 ));
218 }
219 }
220 }
221 Constraint::Regex(pattern) => {
222 let s = val
223 .as_str()
224 .ok_or(format!("{} not string for regex", rule.field))?;
225 let re = Regex::new(pattern).map_err(|e| format!("Invalid regex: {}", e))?;
226 if !re.is_match(s) {
227 return Err(format!("{} regex mismatch: {}", rule.field, pattern));
228 }
229 }
230 }
231 }
232 }
233
234 if let Some(sub_rule) = &rule.rule {
236 match val {
237 Value::Object(_) => validate_field(val, sub_rule)?,
238 Value::Array(arr) => {
239 for v in arr.iter_mut() {
240 validate_field(v, sub_rule)?;
241 }
242 }
243 _ => {}
244 }
245 }
246
247 if let Some(children) = &rule.children {
248 if let Value::Object(_) = val {
249 for child_rule in children {
250 validate_field(val, child_rule)?;
251 }
252 } else {
253 return Err(format!("{} is not object but has children", rule.field));
254 }
255 }
256
257 Ok(())
258}
259
260pub fn validate_type(value: &Value, t: &FieldType) -> Result<(), String> {
261 match t {
262 FieldType::String => {
263 if value.as_str().is_some() {
264 Ok(())
265 } else {
266 Err("Not string".into())
267 }
268 }
269 FieldType::Int => {
270 if value.as_int().is_some() {
271 Ok(())
272 } else {
273 Err("Not int".into())
274 }
275 }
276 FieldType::Float => {
277 if value.as_float().is_some() {
278 Ok(())
279 } else {
280 Err("Not float".into())
281 }
282 }
283 FieldType::Bool => {
284 if value.as_bool().is_some() {
285 Ok(())
286 } else {
287 Err("Not bool".into())
288 }
289 }
290 FieldType::Object => {
291 if value.as_object().is_some() {
292 Ok(())
293 } else {
294 Err("Not object".into())
295 }
296 }
297 FieldType::Array => {
298 if value.as_array().is_some() {
299 Ok(())
300 } else {
301 Err("Not array".into())
302 }
303 }
304 FieldType::Email => {
305 let s = value.as_str().ok_or("Not string for email")?;
306 let re = Regex::new(r"^[^@\s]+@[^@\s]+\.[^@\s]+$").unwrap();
307 if !re.is_match(s) {
308 return Err(format!("{:?} is not a valid email", value));
309 }
310 Ok(())
311 }
312 FieldType::Uri => {
313 let s = value.as_str().ok_or("Not string for uri")?;
314 let url = url::Url::parse(s).map_err(|_| format!("{} is not a valid URI", s))?;
315 Ok(())
316 }
317 FieldType::Uuid => {
318 let s = value.as_str().ok_or("Not string for uuid")?;
319 let re = Regex::new(
320 r"^[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?[0-9a-fA-F]{4}-?[0-9a-fA-F]{4}-?[0-9a-fA-F]{12}$"
321 ).unwrap();
322 if !re.is_match(s) {
323 return Err(format!("{} is not a valid UUID", s));
324 }
325 Ok(())
326 }
327
328 FieldType::Ip => {
329 let s = value.as_str().ok_or("Not string for ip")?;
330 let re =
331 Regex::new(r"^((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[01]?\d\d?)$")
332 .unwrap();
333 if re.is_match(s) {
334 Ok(())
335 } else {
336 Err(format!("Invalid ip: {}", s))
337 }
338 }
339 FieldType::Mac => {
340 let s = value.as_str().ok_or("Not string for mac")?;
341 let re = Regex::new(r"^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$").unwrap();
342 if re.is_match(s) {
343 Ok(())
344 } else {
345 Err(format!("Invalid mac: {}", s))
346 }
347 }
348 FieldType::Date => {
349 let s = value.as_str().ok_or("Not string for date")?;
350 let re = Regex::new(r"^\d{4}-\d{2}-\d{2}$").unwrap();
351 if re.is_match(s) {
352 Ok(())
353 } else {
354 Err(format!("Invalid date: {}", s))
355 }
356 }
357 FieldType::DateTime => {
358 let s = value.as_str().ok_or("Not string for datetime")?;
359 let re = Regex::new(r"^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z?$").unwrap();
360 if re.is_match(s) {
361 Ok(())
362 } else {
363 Err(format!("Invalid datetime: {}", s))
364 }
365 }
366 FieldType::Time => {
367 let s = value.as_str().ok_or("Not string for time")?;
368 let re = Regex::new(r"^\d{2}:\d{2}:\d{2}$").unwrap();
369 if re.is_match(s) {
370 Ok(())
371 } else {
372 Err(format!("Invalid time: {}", s))
373 }
374 }
375 FieldType::Timestamp => {
376 value.as_int().ok_or("Not number for timestamp")?;
377 Ok(())
378 }
379 FieldType::Color => {
380 let s = value.as_str().ok_or("Not string for color")?;
381 let re = Regex::new(r"^#([0-9a-fA-F]{6}|[0-9a-fA-F]{3})$").unwrap();
382 if re.is_match(s) {
383 Ok(())
384 } else {
385 Err(format!("Invalid color: {}", s))
386 }
387 }
388 FieldType::Hostname => {
389 let s = value.as_str().ok_or("Not string for hostname")?;
390 if s.is_empty() || s.len() > 253 {
392 return Err(format!("Invalid hostname length: {}", s));
393 }
394 let re = Regex::new(
396 r"^(?:[a-zA-Z0-9_](?:[a-zA-Z0-9_-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,63}$",
397 )
398 .unwrap();
399
400 if re.is_match(s) {
401 Ok(())
402 } else {
403 Err(format!("Invalid hostname: {}", s))
404 }
405 }
406 FieldType::Slug => {
407 let s = value.as_str().ok_or("Not string for slug")?;
408 let re = Regex::new(r"^[a-z0-9]+(?:-[a-z0-9]+)*$").unwrap();
409 if re.is_match(s) {
410 Ok(())
411 } else {
412 Err(format!("Invalid slug: {}", s))
413 }
414 }
415 FieldType::Hex => {
416 let s = value.as_str().ok_or("Not string for hex")?;
417 let re = Regex::new(r"^[0-9a-fA-F]+$").unwrap();
418 if re.is_match(s) {
419 Ok(())
420 } else {
421 Err(format!("Invalid hex: {}", s))
422 }
423 }
424 FieldType::Base64 => {
425 let s = value.as_str().ok_or("Not string for base64")?;
426 let re = Regex::new(r"^[A-Za-z0-9+/]+={0,2}$").unwrap();
427 if re.is_match(s) {
428 Ok(())
429 } else {
430 Err(format!("Invalid base64: {}", s))
431 }
432 }
433 FieldType::Password => {
434 if value.as_str().is_some() {
435 Ok(())
436 } else {
437 Err("Not string for password".into())
438 }
439 }
440 FieldType::Token => {
441 if value.as_str().is_some() {
442 Ok(())
443 } else {
444 Err("Not string for token".into())
445 }
446 }
447 }
448}
449
450pub fn validate_object(value: &mut Value, rules: &[FieldRule]) -> Result<(), String> {
451 if let Value::Object(_) = value {
452 for rule in rules {
453 validate_field(value, rule)?;
454 }
455 Ok(())
456 } else {
457 Err("Value is not object".into())
458 }
459}
460
461pub fn validate_rule(rule_str: &str, value_str: &str) -> bool {
462 let tokens = match tokenize(rule_str) {
464 Ok(t) => t,
465 Err(_) => {
466 return false;
467 }
468 };
469
470 let mut parser = Parser::new(tokens);
472 let rule_ast = match parser.parse_field(false) {
473 Ok(r) => r,
474 Err(_) => {
475 return false;
476 }
477 };
478
479 let val_enum = match convert_input_to_value(value_str, &rule_ast.field_type) {
481 Ok(v) => v,
482 Err(_) => {
483 return false;
484 }
485 };
486
487 let mut map = HashMap::new();
489 map.insert(rule_ast.field.clone(), val_enum);
490 let mut wrapped_value = Value::Object(map);
491
492 validate_field(&mut wrapped_value, &rule_ast).is_ok()
494}
495
496fn convert_input_to_value(input: &str, target_type: &FieldType) -> Result<Value, String> {
497 match target_type {
498 FieldType::Int => input
499 .parse::<i64>()
500 .map(Value::Int)
501 .map_err(|e| e.to_string()),
502 FieldType::Float => input
503 .parse::<f64>()
504 .map(Value::Float)
505 .map_err(|e| e.to_string()),
506 FieldType::Bool => {
507 match input.to_lowercase().as_str() {
509 "true" | "1" => Ok(Value::Bool(true)),
510 "false" | "0" => Ok(Value::Bool(false)),
511 _ => Err("Invalid boolean value".into()),
512 }
513 }
514 _ => Ok(Value::String(input.to_string())),
515 }
516}