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