1use std::collections::HashSet;
8
9use crate::domain::value_objects::{
10 JsonData, Schema, SchemaValidationError, SchemaValidationResult,
11};
12
13pub struct ValidationService {
36 max_depth: usize,
38}
39
40impl ValidationService {
41 const DEFAULT_MAX_DEPTH: usize = 32;
43
44 pub fn new() -> Self {
46 Self {
47 max_depth: Self::DEFAULT_MAX_DEPTH,
48 }
49 }
50
51 pub fn with_max_depth(max_depth: usize) -> Self {
56 Self { max_depth }
57 }
58
59 pub fn validate(
75 &self,
76 data: &JsonData,
77 schema: &Schema,
78 path: &str,
79 ) -> SchemaValidationResult<()> {
80 self.validate_with_depth(data, schema, path, 0)
81 }
82
83 fn validate_with_depth(
85 &self,
86 data: &JsonData,
87 schema: &Schema,
88 path: &str,
89 depth: usize,
90 ) -> SchemaValidationResult<()> {
91 if depth > self.max_depth {
93 return Err(SchemaValidationError::TypeMismatch {
94 path: path.to_string(),
95 expected: "maximum depth not exceeded".to_string(),
96 actual: format!("depth {depth} exceeds maximum {}", self.max_depth),
97 });
98 }
99
100 match schema {
101 Schema::Any => Ok(()),
102 Schema::Null => self.validate_null(data, path),
103 Schema::Boolean => self.validate_boolean(data, path),
104 Schema::Integer { minimum, maximum } => {
105 self.validate_integer(data, path, *minimum, *maximum)
106 }
107 Schema::Number { minimum, maximum } => {
108 self.validate_number(data, path, *minimum, *maximum)
109 }
110 Schema::String {
111 min_length,
112 max_length,
113 pattern,
114 allowed_values,
115 } => self.validate_string(
116 data,
117 path,
118 *min_length,
119 *max_length,
120 pattern,
121 allowed_values,
122 ),
123 Schema::Array {
124 items,
125 min_items,
126 max_items,
127 unique_items,
128 } => self.validate_array(
129 data,
130 path,
131 items,
132 *min_items,
133 *max_items,
134 *unique_items,
135 depth,
136 ),
137 Schema::Object {
138 properties,
139 required,
140 additional_properties,
141 } => self.validate_object(
142 data,
143 path,
144 properties,
145 required,
146 *additional_properties,
147 depth,
148 ),
149 Schema::OneOf { schemas } => self.validate_one_of(data, path, schemas, depth),
150 Schema::AllOf { schemas } => self.validate_all_of(data, path, schemas, depth),
151 }
152 }
153
154 fn validate_null(&self, data: &JsonData, path: &str) -> SchemaValidationResult<()> {
155 match data {
156 JsonData::Null => Ok(()),
157 _ => Err(SchemaValidationError::TypeMismatch {
158 path: path.to_string(),
159 expected: "null".to_string(),
160 actual: Self::get_type_name(data).to_string(),
161 }),
162 }
163 }
164
165 fn validate_boolean(&self, data: &JsonData, path: &str) -> SchemaValidationResult<()> {
166 match data {
167 JsonData::Bool(_) => Ok(()),
168 _ => Err(SchemaValidationError::TypeMismatch {
169 path: path.to_string(),
170 expected: "boolean".to_string(),
171 actual: Self::get_type_name(data).to_string(),
172 }),
173 }
174 }
175
176 fn get_type_name(data: &JsonData) -> &'static str {
177 match data {
178 JsonData::Null => "null",
179 JsonData::Bool(_) => "boolean",
180 JsonData::Integer(_) => "integer",
181 JsonData::Float(_) => "number",
182 JsonData::String(_) => "string",
183 JsonData::Array(_) => "array",
184 JsonData::Object(_) => "object",
185 }
186 }
187
188 fn validate_integer(
189 &self,
190 data: &JsonData,
191 path: &str,
192 minimum: Option<i64>,
193 maximum: Option<i64>,
194 ) -> SchemaValidationResult<()> {
195 let value = match data {
196 JsonData::Integer(v) => *v,
197 _ => {
198 return Err(SchemaValidationError::TypeMismatch {
199 path: path.to_string(),
200 expected: "integer".to_string(),
201 actual: Self::get_type_name(data).to_string(),
202 });
203 }
204 };
205
206 if let Some(min) = minimum
207 && value < min
208 {
209 return Err(SchemaValidationError::OutOfRange {
210 path: path.to_string(),
211 value: value.to_string(),
212 min: min.to_string(),
213 max: maximum.map_or("∞".to_string(), |m| m.to_string()),
214 });
215 }
216
217 if let Some(max) = maximum
218 && value > max
219 {
220 return Err(SchemaValidationError::OutOfRange {
221 path: path.to_string(),
222 value: value.to_string(),
223 min: minimum.map_or("-∞".to_string(), |m| m.to_string()),
224 max: max.to_string(),
225 });
226 }
227
228 Ok(())
229 }
230
231 fn validate_number(
232 &self,
233 data: &JsonData,
234 path: &str,
235 minimum: Option<f64>,
236 maximum: Option<f64>,
237 ) -> SchemaValidationResult<()> {
238 let value = match data {
239 JsonData::Float(v) => *v,
240 JsonData::Integer(v) => *v as f64,
241 _ => {
242 return Err(SchemaValidationError::TypeMismatch {
243 path: path.to_string(),
244 expected: "number".to_string(),
245 actual: Self::get_type_name(data).to_string(),
246 });
247 }
248 };
249
250 if value.is_nan() || value.is_infinite() {
252 return Err(SchemaValidationError::TypeMismatch {
253 path: path.to_string(),
254 expected: "finite number".to_string(),
255 actual: format!("{}", value),
256 });
257 }
258
259 if let Some(min) = minimum
260 && value < min
261 {
262 return Err(SchemaValidationError::OutOfRange {
263 path: path.to_string(),
264 value: value.to_string(),
265 min: min.to_string(),
266 max: maximum.map_or("∞".to_string(), |m| m.to_string()),
267 });
268 }
269
270 if let Some(max) = maximum
271 && value > max
272 {
273 return Err(SchemaValidationError::OutOfRange {
274 path: path.to_string(),
275 value: value.to_string(),
276 min: minimum.map_or("-∞".to_string(), |m| m.to_string()),
277 max: max.to_string(),
278 });
279 }
280
281 Ok(())
282 }
283
284 fn validate_string(
285 &self,
286 data: &JsonData,
287 path: &str,
288 min_length: Option<usize>,
289 max_length: Option<usize>,
290 _pattern: &Option<String>,
291 allowed_values: &Option<smallvec::SmallVec<[String; 8]>>,
292 ) -> SchemaValidationResult<()> {
293 let value = match data {
294 JsonData::String(s) => s,
295 _ => {
296 return Err(SchemaValidationError::TypeMismatch {
297 path: path.to_string(),
298 expected: "string".to_string(),
299 actual: Self::get_type_name(data).to_string(),
300 });
301 }
302 };
303
304 let len = value.chars().count();
305
306 if let Some(min) = min_length
307 && len < min
308 {
309 return Err(SchemaValidationError::StringLengthConstraint {
310 path: path.to_string(),
311 actual: len,
312 min,
313 max: max_length.unwrap_or(usize::MAX),
314 });
315 }
316
317 if let Some(max) = max_length
318 && len > max
319 {
320 return Err(SchemaValidationError::StringLengthConstraint {
321 path: path.to_string(),
322 actual: len,
323 min: min_length.unwrap_or(0),
324 max,
325 });
326 }
327
328 if let Some(allowed) = allowed_values
329 && !allowed.iter().any(|v| v.as_str() == value)
330 {
331 return Err(SchemaValidationError::InvalidEnumValue {
332 path: path.to_string(),
333 value: value.clone(),
334 });
335 }
336
337 Ok(())
338 }
339
340 #[allow(clippy::too_many_arguments)]
341 fn validate_array(
342 &self,
343 data: &JsonData,
344 path: &str,
345 items: &Option<Box<Schema>>,
346 min_items: Option<usize>,
347 max_items: Option<usize>,
348 unique_items: bool,
349 depth: usize,
350 ) -> SchemaValidationResult<()> {
351 let arr = match data {
352 JsonData::Array(a) => a,
353 _ => {
354 return Err(SchemaValidationError::TypeMismatch {
355 path: path.to_string(),
356 expected: "array".to_string(),
357 actual: Self::get_type_name(data).to_string(),
358 });
359 }
360 };
361
362 let len = arr.len();
363
364 if let Some(min) = min_items
365 && len < min
366 {
367 return Err(SchemaValidationError::ArraySizeConstraint {
368 path: path.to_string(),
369 actual: len,
370 min,
371 max: max_items.unwrap_or(usize::MAX),
372 });
373 }
374
375 if let Some(max) = max_items
376 && len > max
377 {
378 return Err(SchemaValidationError::ArraySizeConstraint {
379 path: path.to_string(),
380 actual: len,
381 min: min_items.unwrap_or(0),
382 max,
383 });
384 }
385
386 if unique_items {
387 let mut seen = HashSet::with_capacity(arr.len());
388 for item in arr {
389 if !seen.insert(item) {
391 return Err(SchemaValidationError::DuplicateItems {
392 path: path.to_string(),
393 });
394 }
395 }
396 }
397
398 if let Some(item_schema) = items {
399 let mut path_buffer = String::with_capacity(path.len() + 16);
401 for (i, item) in arr.iter().enumerate() {
402 path_buffer.clear();
403 path_buffer.push_str(path);
404 path_buffer.push('[');
405 use std::fmt::Write;
406 write!(&mut path_buffer, "{}", i).unwrap();
407 path_buffer.push(']');
408
409 self.validate_with_depth(item, item_schema, &path_buffer, depth + 1)?;
410 }
411 }
412
413 Ok(())
414 }
415
416 fn validate_object(
417 &self,
418 data: &JsonData,
419 path: &str,
420 properties: &std::collections::HashMap<String, Schema>,
421 required: &[String],
422 additional_properties: bool,
423 depth: usize,
424 ) -> SchemaValidationResult<()> {
425 let obj = match data {
426 JsonData::Object(o) => o,
427 _ => {
428 return Err(SchemaValidationError::TypeMismatch {
429 path: path.to_string(),
430 expected: "object".to_string(),
431 actual: Self::get_type_name(data).to_string(),
432 });
433 }
434 };
435
436 for field in required {
438 if !obj.contains_key(field) {
439 return Err(SchemaValidationError::MissingRequired {
440 path: path.to_string(),
441 field: field.clone(),
442 });
443 }
444 }
445
446 let mut path_buffer = String::with_capacity(path.len() + 32);
448 for (key, value) in obj {
449 if let Some(prop_schema) = properties.get(key) {
450 path_buffer.clear();
451 path_buffer.push_str(path);
452 path_buffer.push('/');
453 path_buffer.push_str(key);
454 self.validate_with_depth(value, prop_schema, &path_buffer, depth + 1)?;
455 } else if !additional_properties {
456 return Err(SchemaValidationError::AdditionalPropertyNotAllowed {
457 path: path.to_string(),
458 property: key.clone(),
459 });
460 }
461 }
462
463 Ok(())
464 }
465
466 fn validate_one_of(
467 &self,
468 data: &JsonData,
469 path: &str,
470 schemas: &[Box<Schema>],
471 depth: usize,
472 ) -> SchemaValidationResult<()> {
473 let mut match_count = 0;
474
475 for schema in schemas {
476 if self
477 .validate_with_depth(data, schema, path, depth + 1)
478 .is_ok()
479 {
480 match_count += 1;
481 if match_count > 1 {
483 return Err(SchemaValidationError::NoMatchingOneOf {
484 path: path.to_string(),
485 });
486 }
487 }
488 }
489
490 if match_count == 1 {
491 Ok(())
492 } else {
493 Err(SchemaValidationError::NoMatchingOneOf {
494 path: path.to_string(),
495 })
496 }
497 }
498
499 fn validate_all_of(
500 &self,
501 data: &JsonData,
502 path: &str,
503 schemas: &[Box<Schema>],
504 depth: usize,
505 ) -> SchemaValidationResult<()> {
506 let mut failures = Vec::new();
507
508 for (i, schema) in schemas.iter().enumerate() {
509 if self
510 .validate_with_depth(data, schema, path, depth + 1)
511 .is_err()
512 {
513 failures.push(i);
514 }
515 }
516
517 if failures.is_empty() {
518 Ok(())
519 } else {
520 Err(SchemaValidationError::AllOfFailure {
521 path: path.to_string(),
522 failures: failures
523 .iter()
524 .map(|i| i.to_string())
525 .collect::<Vec<_>>()
526 .join(", "),
527 })
528 }
529 }
530}
531
532impl Default for ValidationService {
533 fn default() -> Self {
534 Self::new()
535 }
536}
537
538#[cfg(test)]
539mod tests {
540 use super::*;
541 use std::collections::HashMap;
542
543 #[test]
544 fn test_validate_null() {
545 let validator = ValidationService::new();
546 let schema = Schema::Null;
547 let data = JsonData::Null;
548
549 assert!(validator.validate(&data, &schema, "/").is_ok());
550
551 let invalid = JsonData::Integer(42);
552 assert!(validator.validate(&invalid, &schema, "/").is_err());
553 }
554
555 #[test]
556 fn test_validate_boolean() {
557 let validator = ValidationService::new();
558 let schema = Schema::Boolean;
559
560 assert!(
561 validator
562 .validate(&JsonData::Bool(true), &schema, "/flag")
563 .is_ok()
564 );
565 assert!(
566 validator
567 .validate(&JsonData::Bool(false), &schema, "/flag")
568 .is_ok()
569 );
570 assert!(
571 validator
572 .validate(&JsonData::Integer(1), &schema, "/flag")
573 .is_err()
574 );
575 }
576
577 #[test]
578 fn test_validate_integer_range() {
579 let validator = ValidationService::new();
580 let schema = Schema::integer(Some(0), Some(100));
581
582 assert!(
583 validator
584 .validate(&JsonData::Integer(50), &schema, "/value")
585 .is_ok()
586 );
587 assert!(
588 validator
589 .validate(&JsonData::Integer(0), &schema, "/value")
590 .is_ok()
591 );
592 assert!(
593 validator
594 .validate(&JsonData::Integer(100), &schema, "/value")
595 .is_ok()
596 );
597 assert!(
598 validator
599 .validate(&JsonData::Integer(-1), &schema, "/value")
600 .is_err()
601 );
602 assert!(
603 validator
604 .validate(&JsonData::Integer(101), &schema, "/value")
605 .is_err()
606 );
607 }
608
609 #[test]
610 fn test_validate_string_length() {
611 let validator = ValidationService::new();
612 let schema = Schema::string(Some(2), Some(10));
613
614 assert!(
615 validator
616 .validate(&JsonData::String("hello".to_string()), &schema, "/")
617 .is_ok()
618 );
619 assert!(
620 validator
621 .validate(&JsonData::String("hi".to_string()), &schema, "/")
622 .is_ok()
623 );
624 assert!(
625 validator
626 .validate(&JsonData::String("0123456789".to_string()), &schema, "/")
627 .is_ok()
628 );
629 assert!(
630 validator
631 .validate(&JsonData::String("a".to_string()), &schema, "/")
632 .is_err()
633 );
634 assert!(
635 validator
636 .validate(&JsonData::String("12345678901".to_string()), &schema, "/")
637 .is_err()
638 );
639 }
640
641 #[test]
642 fn test_validate_array() {
643 let validator = ValidationService::new();
644 let schema = Schema::Array {
645 items: Some(Box::new(Schema::integer(Some(0), None))),
646 min_items: Some(1),
647 max_items: Some(5),
648 unique_items: false,
649 };
650
651 let valid = JsonData::Array(vec![JsonData::Integer(1), JsonData::Integer(2)]);
652 assert!(validator.validate(&valid, &schema, "/items").is_ok());
653
654 let empty = JsonData::Array(vec![]);
655 assert!(validator.validate(&empty, &schema, "/items").is_err());
656
657 let invalid_item = JsonData::Array(vec![JsonData::Integer(-1)]);
658 assert!(
659 validator
660 .validate(&invalid_item, &schema, "/items")
661 .is_err()
662 );
663 }
664
665 #[test]
666 fn test_validate_object() {
667 let validator = ValidationService::new();
668 let mut properties = HashMap::new();
669 properties.insert("id".to_string(), Schema::integer(Some(1), None));
670 properties.insert("name".to_string(), Schema::string(Some(1), Some(100)));
671
672 let schema = Schema::object(properties, vec!["id".to_string()]);
673
674 let mut valid_obj = HashMap::new();
675 valid_obj.insert("id".to_string(), JsonData::Integer(42));
676 valid_obj.insert("name".to_string(), JsonData::String("test".to_string()));
677
678 let valid = JsonData::Object(valid_obj);
679 assert!(validator.validate(&valid, &schema, "/user").is_ok());
680
681 let mut missing_required = HashMap::new();
682 missing_required.insert("name".to_string(), JsonData::String("test".to_string()));
683 let invalid = JsonData::Object(missing_required);
684 assert!(validator.validate(&invalid, &schema, "/user").is_err());
685 }
686
687 #[test]
688 fn test_validate_number() {
689 let validator = ValidationService::new();
690 let schema = Schema::number(Some(0.0), Some(100.0));
691
692 assert!(
693 validator
694 .validate(&JsonData::Float(50.0), &schema, "/value")
695 .is_ok()
696 );
697 assert!(
698 validator
699 .validate(&JsonData::Integer(50), &schema, "/value")
700 .is_ok()
701 );
702 assert!(
703 validator
704 .validate(&JsonData::Float(0.0), &schema, "/value")
705 .is_ok()
706 );
707 assert!(
708 validator
709 .validate(&JsonData::Float(100.0), &schema, "/value")
710 .is_ok()
711 );
712 assert!(
713 validator
714 .validate(&JsonData::Float(-0.1), &schema, "/value")
715 .is_err()
716 );
717 assert!(
718 validator
719 .validate(&JsonData::Float(100.1), &schema, "/value")
720 .is_err()
721 );
722 }
723
724 #[test]
725 fn test_validate_number_nan_infinity() {
726 let validator = ValidationService::new();
727 let schema = Schema::number(Some(0.0), Some(100.0));
728
729 let result = validator.validate(&JsonData::Float(f64::NAN), &schema, "/value");
731 assert!(result.is_err());
732 let err = result.unwrap_err();
733 assert!(matches!(err, SchemaValidationError::TypeMismatch { .. }));
734
735 let result = validator.validate(&JsonData::Float(f64::INFINITY), &schema, "/value");
737 assert!(result.is_err());
738
739 let result = validator.validate(&JsonData::Float(f64::NEG_INFINITY), &schema, "/value");
741 assert!(result.is_err());
742 }
743
744 #[test]
745 fn test_validate_string_enum_values() {
746 let validator = ValidationService::new();
747 use smallvec::SmallVec;
748
749 let allowed_values = Some(SmallVec::from_vec(vec![
750 String::from("red"),
751 String::from("green"),
752 String::from("blue"),
753 ]));
754
755 let schema = Schema::String {
756 min_length: None,
757 max_length: None,
758 pattern: None,
759 allowed_values,
760 };
761
762 assert!(
764 validator
765 .validate(&JsonData::String("red".to_string()), &schema, "/color")
766 .is_ok()
767 );
768 assert!(
769 validator
770 .validate(&JsonData::String("green".to_string()), &schema, "/color")
771 .is_ok()
772 );
773 assert!(
774 validator
775 .validate(&JsonData::String("blue".to_string()), &schema, "/color")
776 .is_ok()
777 );
778
779 let result = validator.validate(&JsonData::String("yellow".to_string()), &schema, "/color");
781 assert!(result.is_err());
782 let err = result.unwrap_err();
783 assert!(matches!(
784 err,
785 SchemaValidationError::InvalidEnumValue { .. }
786 ));
787 }
788
789 #[test]
790 fn test_validate_array_unique_items() {
791 let validator = ValidationService::new();
792 let schema = Schema::Array {
793 items: Some(Box::new(Schema::integer(None, None))),
794 min_items: None,
795 max_items: None,
796 unique_items: true,
797 };
798
799 let unique = JsonData::Array(vec![
801 JsonData::Integer(1),
802 JsonData::Integer(2),
803 JsonData::Integer(3),
804 ]);
805 assert!(validator.validate(&unique, &schema, "/items").is_ok());
806
807 let duplicates = JsonData::Array(vec![
809 JsonData::Integer(1),
810 JsonData::Integer(2),
811 JsonData::Integer(1),
812 ]);
813 let result = validator.validate(&duplicates, &schema, "/items");
814 assert!(result.is_err());
815 let err = result.unwrap_err();
816 assert!(matches!(err, SchemaValidationError::DuplicateItems { .. }));
817 }
818
819 #[test]
820 fn test_validate_array_min_max_items() {
821 let validator = ValidationService::new();
822 let schema = Schema::Array {
823 items: None,
824 min_items: Some(2),
825 max_items: Some(4),
826 unique_items: false,
827 };
828
829 let valid = JsonData::Array(vec![JsonData::Integer(1), JsonData::Integer(2)]);
831 assert!(validator.validate(&valid, &schema, "/items").is_ok());
832
833 let too_few = JsonData::Array(vec![JsonData::Integer(1)]);
835 let result = validator.validate(&too_few, &schema, "/items");
836 assert!(result.is_err());
837 let err = result.unwrap_err();
838 assert!(matches!(
839 err,
840 SchemaValidationError::ArraySizeConstraint { .. }
841 ));
842
843 let too_many = JsonData::Array(vec![
845 JsonData::Integer(1),
846 JsonData::Integer(2),
847 JsonData::Integer(3),
848 JsonData::Integer(4),
849 JsonData::Integer(5),
850 ]);
851 let result = validator.validate(&too_many, &schema, "/items");
852 assert!(result.is_err());
853 assert!(matches!(
854 result.unwrap_err(),
855 SchemaValidationError::ArraySizeConstraint { .. }
856 ));
857 }
858
859 #[test]
860 fn test_validate_object_additional_properties() {
861 let validator = ValidationService::new();
862 let mut properties = HashMap::new();
863 properties.insert("name".to_string(), Schema::string(Some(1), Some(100)));
864
865 let schema = Schema::Object {
867 properties: properties.clone(),
868 required: vec![],
869 additional_properties: false,
870 };
871
872 let mut valid_obj = HashMap::new();
873 valid_obj.insert("name".to_string(), JsonData::String("test".to_string()));
874
875 let valid = JsonData::Object(valid_obj.clone());
877 assert!(validator.validate(&valid, &schema, "/obj").is_ok());
878
879 let mut invalid_obj = valid_obj;
881 invalid_obj.insert("extra".to_string(), JsonData::Integer(42));
882 let invalid = JsonData::Object(invalid_obj);
883 let result = validator.validate(&invalid, &schema, "/obj");
884 assert!(result.is_err());
885 let err = result.unwrap_err();
886 assert!(matches!(
887 err,
888 SchemaValidationError::AdditionalPropertyNotAllowed { .. }
889 ));
890
891 let schema_allow = Schema::Object {
893 properties,
894 required: vec![],
895 additional_properties: true,
896 };
897
898 let mut obj_with_extra = HashMap::new();
899 obj_with_extra.insert("name".to_string(), JsonData::String("test".to_string()));
900 obj_with_extra.insert("extra".to_string(), JsonData::Integer(42));
901 let with_extra = JsonData::Object(obj_with_extra);
902 assert!(
903 validator
904 .validate(&with_extra, &schema_allow, "/obj")
905 .is_ok()
906 );
907 }
908
909 #[test]
910 fn test_validate_one_of_single_match() {
911 let validator = ValidationService::new();
912 use smallvec::SmallVec;
913
914 let schema = Schema::OneOf {
915 schemas: SmallVec::from_vec(vec![
916 Box::new(Schema::string(Some(1), None)),
917 Box::new(Schema::integer(Some(0), None)),
918 ]),
919 };
920
921 assert!(
923 validator
924 .validate(&JsonData::String("test".to_string()), &schema, "/value")
925 .is_ok()
926 );
927
928 assert!(
930 validator
931 .validate(&JsonData::Integer(42), &schema, "/value")
932 .is_ok()
933 );
934 }
935
936 #[test]
937 fn test_validate_one_of_no_match() {
938 let validator = ValidationService::new();
939 use smallvec::SmallVec;
940
941 let schema = Schema::OneOf {
942 schemas: SmallVec::from_vec(vec![
943 Box::new(Schema::string(Some(5), None)), Box::new(Schema::integer(Some(100), None)), ]),
946 };
947
948 let result = validator.validate(&JsonData::String("hi".to_string()), &schema, "/value");
950 assert!(result.is_err());
951 assert!(matches!(
952 result.unwrap_err(),
953 SchemaValidationError::NoMatchingOneOf { .. }
954 ));
955
956 let result = validator.validate(&JsonData::Integer(50), &schema, "/value");
958 assert!(result.is_err());
959 }
960
961 #[test]
962 fn test_validate_one_of_multiple_matches() {
963 let validator = ValidationService::new();
964 use smallvec::SmallVec;
965
966 let schema = Schema::OneOf {
967 schemas: SmallVec::from_vec(vec![
968 Box::new(Schema::integer(None, None)), Box::new(Schema::integer(Some(0), Some(100))), ]),
971 };
972
973 let result = validator.validate(&JsonData::Integer(50), &schema, "/value");
975 assert!(result.is_err());
976 assert!(matches!(
977 result.unwrap_err(),
978 SchemaValidationError::NoMatchingOneOf { .. }
979 ));
980 }
981
982 #[test]
983 fn test_validate_all_of_success() {
984 let validator = ValidationService::new();
985 use smallvec::SmallVec;
986
987 let schema = Schema::AllOf {
988 schemas: SmallVec::from_vec(vec![
989 Box::new(Schema::integer(Some(0), None)), Box::new(Schema::integer(None, Some(100))), ]),
992 };
993
994 assert!(
996 validator
997 .validate(&JsonData::Integer(50), &schema, "/value")
998 .is_ok()
999 );
1000 assert!(
1001 validator
1002 .validate(&JsonData::Integer(0), &schema, "/value")
1003 .is_ok()
1004 );
1005 assert!(
1006 validator
1007 .validate(&JsonData::Integer(100), &schema, "/value")
1008 .is_ok()
1009 );
1010 }
1011
1012 #[test]
1013 fn test_validate_all_of_failure() {
1014 let validator = ValidationService::new();
1015 use smallvec::SmallVec;
1016
1017 let schema = Schema::AllOf {
1018 schemas: SmallVec::from_vec(vec![
1019 Box::new(Schema::integer(Some(0), None)), Box::new(Schema::integer(None, Some(100))), ]),
1022 };
1023
1024 let result = validator.validate(&JsonData::Integer(-1), &schema, "/value");
1026 assert!(result.is_err());
1027 let err = result.unwrap_err();
1028 assert!(matches!(err, SchemaValidationError::AllOfFailure { .. }));
1029
1030 let result = validator.validate(&JsonData::Integer(101), &schema, "/value");
1032 assert!(result.is_err());
1033 assert!(matches!(
1034 result.unwrap_err(),
1035 SchemaValidationError::AllOfFailure { .. }
1036 ));
1037 }
1038
1039 #[test]
1040 fn test_validate_max_depth_exceeded() {
1041 let validator = ValidationService::with_max_depth(5);
1042
1043 fn create_nested(depth: usize) -> JsonData {
1045 if depth == 0 {
1046 JsonData::Integer(42)
1047 } else {
1048 let mut obj = HashMap::new();
1049 obj.insert("nested".to_string(), create_nested(depth - 1));
1050 JsonData::Object(obj)
1051 }
1052 }
1053
1054 fn create_nested_schema(depth: usize) -> Schema {
1055 if depth == 0 {
1056 Schema::integer(None, None)
1057 } else {
1058 Schema::Object {
1059 properties: [("nested".to_string(), create_nested_schema(depth - 1))]
1060 .into_iter()
1061 .collect(),
1062 required: vec![],
1063 additional_properties: false,
1064 }
1065 }
1066 }
1067
1068 let data = create_nested(10);
1069 let schema = create_nested_schema(10);
1070
1071 let result = validator.validate(&data, &schema, "/deep");
1073 assert!(result.is_err());
1074 let err = result.unwrap_err();
1075 assert!(matches!(err, SchemaValidationError::TypeMismatch { .. }));
1076 }
1077
1078 #[test]
1079 fn test_validate_any_schema() {
1080 let validator = ValidationService::new();
1081 let schema = Schema::Any;
1082
1083 assert!(validator.validate(&JsonData::Null, &schema, "/").is_ok());
1085 assert!(
1086 validator
1087 .validate(&JsonData::Bool(true), &schema, "/")
1088 .is_ok()
1089 );
1090 assert!(
1091 validator
1092 .validate(&JsonData::Integer(42), &schema, "/")
1093 .is_ok()
1094 );
1095 assert!(
1096 validator
1097 .validate(&JsonData::Float(std::f64::consts::PI), &schema, "/")
1098 .is_ok()
1099 );
1100 assert!(
1101 validator
1102 .validate(&JsonData::String("test".to_string()), &schema, "/")
1103 .is_ok()
1104 );
1105 assert!(
1106 validator
1107 .validate(&JsonData::Array(vec![]), &schema, "/")
1108 .is_ok()
1109 );
1110 assert!(
1111 validator
1112 .validate(&JsonData::Object(HashMap::new()), &schema, "/")
1113 .is_ok()
1114 );
1115 }
1116
1117 #[test]
1118 fn test_validate_type_mismatches() {
1119 let validator = ValidationService::new();
1120
1121 let test_cases = vec![
1123 (Schema::Null, JsonData::Integer(42), "null"),
1124 (
1125 Schema::Boolean,
1126 JsonData::String("true".to_string()),
1127 "boolean",
1128 ),
1129 (
1130 Schema::integer(None, None),
1131 JsonData::String("42".to_string()),
1132 "integer",
1133 ),
1134 (
1135 Schema::number(None, None),
1136 JsonData::String("3.14".to_string()),
1137 "number",
1138 ),
1139 (Schema::string(None, None), JsonData::Integer(42), "string"),
1140 (
1141 Schema::Array {
1142 items: None,
1143 min_items: None,
1144 max_items: None,
1145 unique_items: false,
1146 },
1147 JsonData::Integer(42),
1148 "array",
1149 ),
1150 (
1151 Schema::Object {
1152 properties: HashMap::new(),
1153 required: vec![],
1154 additional_properties: true,
1155 },
1156 JsonData::Integer(42),
1157 "object",
1158 ),
1159 ];
1160
1161 for (schema, data, expected_type) in test_cases {
1162 let result = validator.validate(&data, &schema, "/test");
1163 assert!(result.is_err(), "Expected error for {expected_type}");
1164 let err = result.unwrap_err();
1165 assert!(
1166 matches!(err, SchemaValidationError::TypeMismatch { .. }),
1167 "Expected TypeMismatch for {expected_type}"
1168 );
1169 }
1170 }
1171
1172 #[test]
1173 fn test_default_validation_service() {
1174 let default = ValidationService::default();
1175 let created = ValidationService::new();
1176
1177 let schema = Schema::integer(None, None);
1179 let data = JsonData::Integer(42);
1180
1181 assert!(default.validate(&data, &schema, "/").is_ok());
1182 assert!(created.validate(&data, &schema, "/").is_ok());
1183 }
1184}