1use super::JSONEval;
2use crate::jsoneval::cancellation::CancellationToken;
3use crate::jsoneval::json_parser;
4use crate::jsoneval::path_utils;
5use crate::jsoneval::types::{ValidationError, ValidationResult};
6
7use crate::time_block;
8
9use indexmap::IndexMap;
10use serde_json::Value;
11
12impl JSONEval {
13 pub fn validate(
15 &mut self,
16 data: &str,
17 context: Option<&str>,
18 paths: Option<&[String]>,
19 token: Option<&CancellationToken>,
20 ) -> Result<ValidationResult, String> {
21 if let Some(t) = token {
22 if t.is_cancelled() {
23 return Err("Cancelled".to_string());
24 }
25 }
26 time_block!("validate() [total]", {
27 let _lock = self.eval_lock.lock().unwrap();
29
30 let data_value = json_parser::parse_json_str(data)?;
32 let context_value = if let Some(ctx) = context {
33 json_parser::parse_json_str(ctx)?
34 } else {
35 Value::Object(serde_json::Map::new())
36 };
37
38 self.context = context_value.clone();
40
41 self.eval_data
43 .replace_data_and_context(data_value.clone(), context_value);
44
45 drop(_lock);
47
48 self.evaluate_others(paths, token, true);
52
53 self.evaluated_schema = self.get_evaluated_schema(false);
55
56 let mut errors: IndexMap<String, ValidationError> = IndexMap::new();
57
58 for field_path in self.fields_with_rules.iter() {
61 if let Some(filter_paths) = paths {
63 if !filter_paths.is_empty()
64 && !filter_paths.iter().any(|p| {
65 field_path.starts_with(p.as_str()) || p.starts_with(field_path.as_str())
66 })
67 {
68 continue;
69 }
70 }
71
72 self.validate_field(field_path, &data_value, &mut errors);
73
74 if let Some(t) = token {
75 if t.is_cancelled() {
76 return Err("Cancelled".to_string());
77 }
78 }
79 }
80
81 let has_error = !errors.is_empty();
82
83 Ok(ValidationResult { has_error, errors })
84 })
85 }
86
87 pub(crate) fn validate_pre_set(
92 &mut self,
93 data_value: Value,
94 paths: Option<&[String]>,
95 token: Option<&CancellationToken>,
96 ) -> Result<crate::ValidationResult, String> {
97 self.evaluate_others(paths, token, true);
99 self.evaluated_schema = self.get_evaluated_schema(false);
100
101 let mut errors: IndexMap<String, ValidationError> = IndexMap::new();
102
103 let fields: Vec<String> = self.fields_with_rules.iter().cloned().collect();
104 for field_path in &fields {
105 if let Some(filter_paths) = paths {
106 if !filter_paths.is_empty()
107 && !filter_paths.iter().any(|p| {
108 field_path.starts_with(p.as_str()) || p.starts_with(field_path.as_str())
109 })
110 {
111 continue;
112 }
113 }
114 if let Some(t) = token {
115 if t.is_cancelled() {
116 return Err("Cancelled".to_string());
117 }
118 }
119 self.validate_field(field_path, &data_value, &mut errors);
120 }
121
122 let has_error = !errors.is_empty();
123 Ok(crate::ValidationResult { has_error, errors })
124 }
125
126 pub(crate) fn validate_field(
128 &self,
129 field_path: &str,
130 data: &Value,
131 errors: &mut IndexMap<String, ValidationError>,
132 ) {
133 if errors.contains_key(field_path) {
135 return;
136 }
137
138 let schema_path = path_utils::dot_notation_to_schema_pointer(field_path);
140 let pointer_path = schema_path.trim_start_matches('#');
141
142 let (field_schema, resolved_path) = match self.evaluated_schema.pointer(pointer_path) {
144 Some(s) => (s, pointer_path.to_string()),
145 None => {
146 let alt_path = format!("/properties{}", pointer_path);
147 match self.evaluated_schema.pointer(&alt_path) {
148 Some(s) => (s, alt_path),
149 None => return,
150 }
151 }
152 };
153
154 if self.is_effective_hidden(&resolved_path) {
156 return;
157 }
158
159 if let Value::Object(schema_map) = field_schema {
160 let rules = match schema_map.get("rules") {
162 Some(Value::Object(r)) => r,
163 _ => return,
164 };
165
166 let field_data = self.get_field_data(field_path, data);
168
169 for (rule_name, rule_value) in rules {
171 self.validate_rule(
172 field_path,
173 rule_name,
174 rule_value,
175 &field_data,
176 schema_map,
177 field_schema,
178 errors,
179 );
180 }
181 }
182 }
183
184 pub(crate) fn get_field_data(&self, field_path: &str, data: &Value) -> Value {
186 let parts: Vec<&str> = field_path.split('.').collect();
187 let mut current = data;
188
189 for part in parts {
190 match current {
191 Value::Object(map) => {
192 current = map.get(part).unwrap_or(&Value::Null);
193 }
194 _ => return Value::Null,
195 }
196 }
197
198 current.clone()
199 }
200
201 #[allow(clippy::too_many_arguments)]
203 pub(crate) fn validate_rule(
204 &self,
205 field_path: &str,
206 rule_name: &str,
207 rule_value: &Value,
208 field_data: &Value,
209 schema_map: &serde_json::Map<String, Value>,
210 _schema: &Value,
211 errors: &mut IndexMap<String, ValidationError>,
212 ) {
213 if errors.contains_key(field_path) {
215 return;
216 }
217
218 let mut disabled_field = false;
219 if let Some(Value::Object(condition)) = schema_map.get("condition") {
221 if let Some(Value::Bool(true)) = condition.get("disabled") {
222 disabled_field = true;
223 }
224 }
225
226 let schema_path = path_utils::dot_notation_to_schema_pointer(field_path);
229 let rule_path = format!(
230 "{}/rules/{}",
231 schema_path.trim_start_matches('#'),
232 rule_name
233 );
234
235 let evaluated_rule = if let Some(eval_rule) = self.evaluated_schema.pointer(&rule_path) {
237 eval_rule.clone()
238 } else {
239 rule_value.clone()
240 };
241
242 let (rule_active, rule_message, rule_code, rule_data) = match &evaluated_rule {
246 Value::Object(rule_obj) => {
247 let active = rule_obj.get("value").unwrap_or(&Value::Bool(false));
248
249 let message = match rule_obj.get("message") {
251 Some(Value::String(s)) => s.clone(),
252 Some(Value::Object(msg_obj)) if msg_obj.contains_key("value") => msg_obj
253 .get("value")
254 .and_then(|v| v.as_str())
255 .unwrap_or("Validation failed")
256 .to_string(),
257 Some(msg_val) => msg_val.as_str().unwrap_or("Validation failed").to_string(),
258 None => "Validation failed".to_string(),
259 };
260
261 let code = rule_obj
262 .get("code")
263 .and_then(|c| c.as_str())
264 .map(|s| s.to_string());
265
266 let data = rule_obj.get("data").map(|d| {
268 if let Value::Object(data_obj) = d {
269 let mut cleaned_data = serde_json::Map::new();
270 for (key, value) in data_obj {
271 if let Value::Object(val_obj) = value {
273 if val_obj.len() == 1 && val_obj.contains_key("value") {
274 cleaned_data.insert(key.clone(), val_obj["value"].clone());
275 } else {
276 cleaned_data.insert(key.clone(), value.clone());
277 }
278 } else {
279 cleaned_data.insert(key.clone(), value.clone());
280 }
281 }
282 Value::Object(cleaned_data)
283 } else {
284 d.clone()
285 }
286 });
287
288 (active.clone(), message, code, data)
289 }
290 _ => (
291 evaluated_rule.clone(),
292 "Validation failed".to_string(),
293 None,
294 None,
295 ),
296 };
297
298 let error_code = rule_code.or_else(|| Some(format!("{}.{}", field_path, rule_name)));
300
301 let is_empty = matches!(field_data, Value::Null)
302 || (field_data.is_string() && field_data.as_str().unwrap_or("").is_empty())
303 || (field_data.is_array() && field_data.as_array().unwrap().is_empty());
304
305 match rule_name {
306 "required" => {
307 if !disabled_field && rule_active == Value::Bool(true) {
308 if is_empty {
309 errors.insert(
310 field_path.to_string(),
311 ValidationError {
312 rule_type: "required".to_string(),
313 message: rule_message,
314 code: error_code.clone(),
315 pattern: None,
316 field_value: None,
317 data: None,
318 },
319 );
320 }
321 }
322 }
323 "minLength" => {
324 if !is_empty {
325 if let Some(min) = rule_active.as_u64() {
326 let len = match field_data {
327 Value::String(s) => s.len(),
328 Value::Array(a) => a.len(),
329 _ => 0,
330 };
331 if len < min as usize {
332 errors.insert(
333 field_path.to_string(),
334 ValidationError {
335 rule_type: "minLength".to_string(),
336 message: rule_message,
337 code: error_code.clone(),
338 pattern: None,
339 field_value: None,
340 data: None,
341 },
342 );
343 }
344 }
345 }
346 }
347 "maxLength" => {
348 if !is_empty {
349 if let Some(max) = rule_active.as_u64() {
350 let len = match field_data {
351 Value::String(s) => s.len(),
352 Value::Array(a) => a.len(),
353 _ => 0,
354 };
355 if len > max as usize {
356 errors.insert(
357 field_path.to_string(),
358 ValidationError {
359 rule_type: "maxLength".to_string(),
360 message: rule_message,
361 code: error_code.clone(),
362 pattern: None,
363 field_value: None,
364 data: None,
365 },
366 );
367 }
368 }
369 }
370 }
371 "minValue" => {
372 if !is_empty {
373 if let Some(min) = rule_active.as_f64() {
374 if let Some(val) = field_data.as_f64() {
375 if val < min {
376 errors.insert(
377 field_path.to_string(),
378 ValidationError {
379 rule_type: "minValue".to_string(),
380 message: rule_message,
381 code: error_code.clone(),
382 pattern: None,
383 field_value: None,
384 data: None,
385 },
386 );
387 }
388 }
389 }
390 }
391 }
392 "maxValue" => {
393 if !is_empty {
394 if let Some(max) = rule_active.as_f64() {
395 if let Some(val) = field_data.as_f64() {
396 if val > max {
397 errors.insert(
398 field_path.to_string(),
399 ValidationError {
400 rule_type: "maxValue".to_string(),
401 message: rule_message,
402 code: error_code.clone(),
403 pattern: None,
404 field_value: None,
405 data: None,
406 },
407 );
408 }
409 }
410 }
411 }
412 }
413 "pattern" => {
414 if !is_empty {
415 if let Some(pattern) = rule_active.as_str() {
416 if let Some(text) = field_data.as_str() {
417 let mut cache = self.regex_cache.write().unwrap();
418 let regex = cache.entry(pattern.to_string()).or_insert_with(|| {
419 regex::Regex::new(pattern)
420 .unwrap_or_else(|_| regex::Regex::new("(?:)").unwrap())
421 });
422 if !regex.is_match(text) {
423 errors.insert(
424 field_path.to_string(),
425 ValidationError {
426 rule_type: "pattern".to_string(),
427 message: rule_message,
428 code: error_code.clone(),
429 pattern: Some(pattern.to_string()),
430 field_value: Some(text.to_string()),
431 data: None,
432 },
433 );
434 }
435 }
436 }
437 }
438 }
439 "evaluation" => {
440 if let Value::Array(eval_array) = &evaluated_rule {
443 for (idx, eval_item) in eval_array.iter().enumerate() {
444 if let Value::Object(eval_obj) = eval_item {
445 let eval_result = eval_obj.get("value").unwrap_or(&Value::Bool(true));
447
448 let is_falsy = match eval_result {
450 Value::Bool(false) => true,
451 Value::Null => true,
452 Value::Number(n) => n.as_f64() == Some(0.0),
453 Value::String(s) => s.is_empty(),
454 Value::Array(a) => a.is_empty(),
455 _ => false,
456 };
457
458 if is_falsy {
459 let eval_code = eval_obj
460 .get("code")
461 .and_then(|c| c.as_str())
462 .map(|s| s.to_string())
463 .or_else(|| Some(format!("{}.evaluation.{}", field_path, idx)));
464
465 let eval_message = eval_obj
466 .get("message")
467 .and_then(|m| m.as_str())
468 .unwrap_or("Validation failed")
469 .to_string();
470
471 let eval_data = eval_obj.get("data").cloned();
472
473 errors.insert(
474 field_path.to_string(),
475 ValidationError {
476 rule_type: "evaluation".to_string(),
477 message: eval_message,
478 code: eval_code,
479 pattern: None,
480 field_value: None,
481 data: eval_data,
482 },
483 );
484
485 break;
487 }
488 }
489 }
490 }
491 }
492 _ => {
493 if !is_empty {
497 let is_falsy = match &rule_active {
499 Value::Bool(false) => true,
500 Value::Null => true,
501 Value::Number(n) => n.as_f64() == Some(0.0),
502 Value::String(s) => s.is_empty(),
503 Value::Array(a) => a.is_empty(),
504 _ => false,
505 };
506
507 if is_falsy {
508 errors.insert(
509 field_path.to_string(),
510 ValidationError {
511 rule_type: "evaluation".to_string(),
512 message: rule_message,
513 code: error_code.clone(),
514 pattern: None,
515 field_value: None,
516 data: rule_data,
517 },
518 );
519 }
520 }
521 }
522 }
523 }
524}