1use base64::Engine;
6use chrono::{NaiveDate, NaiveTime, DateTime};
7use regex::Regex;
8use serde_json::Value;
9use uuid::Uuid;
10
11use crate::error_codes::InstanceErrorCode;
12use crate::json_source_locator::JsonSourceLocator;
13use crate::types::{
14 InstanceValidatorOptions, JsonLocation, ValidationError, ValidationResult,
15};
16
17pub struct InstanceValidator {
35 options: InstanceValidatorOptions,
36}
37
38impl Default for InstanceValidator {
39 fn default() -> Self {
40 Self::new()
41 }
42}
43
44impl InstanceValidator {
45 #[must_use]
47 pub fn new() -> Self {
48 Self::with_options(InstanceValidatorOptions::default())
49 }
50
51 #[must_use]
53 pub fn with_options(options: InstanceValidatorOptions) -> Self {
54 Self { options }
55 }
56
57 pub fn set_extended(&mut self, extended: bool) {
59 self.options.extended = extended;
60 }
61
62 #[must_use]
64 pub fn is_extended(&self) -> bool {
65 self.options.extended
66 }
67
68 #[must_use]
72 pub fn validate(&self, instance_json: &str, schema: &Value) -> ValidationResult {
73 let mut result = ValidationResult::new();
74 let locator = JsonSourceLocator::new(instance_json);
75
76 match serde_json::from_str::<Value>(instance_json) {
77 Ok(instance) => {
78 let effective_schema = if let Some(schema_obj) = schema.as_object() {
80 if let Some(Value::String(root_ref)) = schema_obj.get("$root") {
81 if let Some(resolved) = self.resolve_ref(root_ref, schema) {
83 resolved
84 } else {
85 schema
87 }
88 } else {
89 schema
90 }
91 } else {
92 schema
93 };
94
95 self.validate_instance(&instance, effective_schema, schema, &mut result, "", &locator, 0);
96 }
97 Err(e) => {
98 result.add_error(ValidationError::instance_error(
99 InstanceErrorCode::InstanceTypeMismatch,
100 format!("Failed to parse JSON: {}", e),
101 "",
102 JsonLocation::unknown(),
103 ));
104 }
105 }
106
107 result
108 }
109
110 fn validate_instance(
112 &self,
113 instance: &Value,
114 schema: &Value,
115 root_schema: &Value,
116 result: &mut ValidationResult,
117 path: &str,
118 locator: &JsonSourceLocator,
119 depth: usize,
120 ) {
121 if depth > 64 {
122 return;
123 }
124
125 match schema {
127 Value::Bool(true) => return, Value::Bool(false) => {
129 result.add_error(ValidationError::instance_error(
130 InstanceErrorCode::InstanceTypeMismatch,
131 "Schema rejects all values",
132 path,
133 locator.get_location(path),
134 ));
135 return;
136 }
137 Value::Object(_) => {}
138 _ => return,
139 }
140
141 let schema_obj = schema.as_object().unwrap();
142
143 if let Some(ref_val) = schema_obj.get("$ref") {
145 if let Value::String(ref_str) = ref_val {
146 if let Some(resolved) = self.resolve_ref(ref_str, root_schema) {
147 self.validate_instance(instance, resolved, root_schema, result, path, locator, depth + 1);
148 return;
149 } else {
150 result.add_error(ValidationError::instance_error(
151 InstanceErrorCode::InstanceRefNotFound,
152 format!("Reference not found: {}", ref_str),
153 path,
154 locator.get_location(path),
155 ));
156 return;
157 }
158 }
159 }
160
161 if let Some(enum_val) = schema_obj.get("enum") {
163 if !self.validate_enum(instance, enum_val) {
164 result.add_error(ValidationError::instance_error(
165 InstanceErrorCode::InstanceEnumMismatch,
166 "Value does not match any enum value",
167 path,
168 locator.get_location(path),
169 ));
170 return;
171 }
172 }
173
174 if let Some(const_val) = schema_obj.get("const") {
176 if instance != const_val {
177 result.add_error(ValidationError::instance_error(
178 InstanceErrorCode::InstanceConstMismatch,
179 "Value does not match const",
180 path,
181 locator.get_location(path),
182 ));
183 return;
184 }
185 }
186
187 if let Some(type_val) = schema_obj.get("type") {
189 match type_val {
190 Value::String(type_name) => {
191 self.validate_type(instance, type_name, schema_obj, root_schema, result, path, locator, depth);
192 }
193 Value::Array(types) => {
194 let mut union_valid = false;
196 for t in types {
197 match t {
198 Value::String(type_name) => {
199 let mut temp_result = ValidationResult::new();
200 self.validate_type(instance, type_name, schema_obj, root_schema, &mut temp_result, path, locator, depth);
201 if temp_result.is_valid() {
202 union_valid = true;
203 break;
204 }
205 }
206 Value::Object(ref_obj) => {
207 if let Some(Value::String(ref_str)) = ref_obj.get("$ref") {
208 if let Some(resolved) = self.resolve_ref(ref_str, root_schema) {
209 let mut temp_result = ValidationResult::new();
210 self.validate_instance(instance, resolved, root_schema, &mut temp_result, path, locator, depth + 1);
211 if temp_result.is_valid() {
212 union_valid = true;
213 break;
214 }
215 }
216 }
217 }
218 _ => {}
219 }
220 }
221 if !union_valid {
222 result.add_error(ValidationError::instance_error(
223 InstanceErrorCode::InstanceUnionNoMatch,
224 "Value does not match any type in union",
225 path,
226 locator.get_location(path),
227 ));
228 }
229 return;
230 }
231 Value::Object(ref_obj) => {
232 if let Some(Value::String(ref_str)) = ref_obj.get("$ref") {
234 if let Some(resolved) = self.resolve_ref(ref_str, root_schema) {
235 self.validate_instance(instance, resolved, root_schema, result, path, locator, depth + 1);
236 } else {
237 result.add_error(ValidationError::instance_error(
238 InstanceErrorCode::InstanceRefNotFound,
239 format!("Reference not found: {}", ref_str),
240 path,
241 locator.get_location(path),
242 ));
243 }
244 }
245 return;
246 }
247 _ => {}
248 }
249 }
250
251 if self.options.extended {
253 self.validate_composition(instance, schema_obj, root_schema, result, path, locator, depth);
254 }
255 }
256
257 fn resolve_ref<'a>(&self, ref_str: &str, root_schema: &'a Value) -> Option<&'a Value> {
259 if let Some(def_name) = ref_str.strip_prefix("#/definitions/") {
260 root_schema
261 .get("definitions")
262 .and_then(|defs| defs.get(def_name))
263 } else {
264 None
265 }
266 }
267
268 fn validate_enum(&self, instance: &Value, enum_val: &Value) -> bool {
270 if let Value::Array(arr) = enum_val {
271 arr.iter().any(|v| v == instance)
272 } else {
273 false
274 }
275 }
276
277 fn validate_type(
279 &self,
280 instance: &Value,
281 type_name: &str,
282 schema_obj: &serde_json::Map<String, Value>,
283 root_schema: &Value,
284 result: &mut ValidationResult,
285 path: &str,
286 locator: &JsonSourceLocator,
287 depth: usize,
288 ) {
289 match type_name {
290 "string" => self.validate_string(instance, schema_obj, result, path, locator),
291 "boolean" => self.validate_boolean(instance, result, path, locator),
292 "null" => self.validate_null(instance, result, path, locator),
293 "number" => self.validate_number(instance, schema_obj, result, path, locator),
294 "integer" | "int32" => self.validate_int32(instance, schema_obj, result, path, locator),
295 "int8" => self.validate_int_range(instance, schema_obj, result, path, locator, -128, 127, "int8"),
296 "int16" => self.validate_int_range(instance, schema_obj, result, path, locator, -32768, 32767, "int16"),
297 "int64" => self.validate_int64(instance, schema_obj, result, path, locator),
298 "int128" => self.validate_int128(instance, schema_obj, result, path, locator),
299 "uint8" => self.validate_uint_range(instance, schema_obj, result, path, locator, 0, 255, "uint8"),
300 "uint16" => self.validate_uint_range(instance, schema_obj, result, path, locator, 0, 65535, "uint16"),
301 "uint32" => self.validate_uint32(instance, schema_obj, result, path, locator),
302 "uint64" => self.validate_uint64(instance, schema_obj, result, path, locator),
303 "uint128" => self.validate_uint128(instance, schema_obj, result, path, locator),
304 "float" | "float8" | "double" | "decimal" => {
305 self.validate_number(instance, schema_obj, result, path, locator)
306 }
307 "date" => self.validate_date(instance, result, path, locator),
308 "time" => self.validate_time(instance, result, path, locator),
309 "datetime" => self.validate_datetime(instance, result, path, locator),
310 "duration" => self.validate_duration(instance, result, path, locator),
311 "uuid" => self.validate_uuid(instance, result, path, locator),
312 "uri" => self.validate_uri(instance, result, path, locator),
313 "binary" => self.validate_binary(instance, result, path, locator),
314 "jsonpointer" => self.validate_jsonpointer(instance, result, path, locator),
315 "object" => self.validate_object(instance, schema_obj, root_schema, result, path, locator, depth),
316 "array" => self.validate_array(instance, schema_obj, root_schema, result, path, locator, depth),
317 "set" => self.validate_set(instance, schema_obj, root_schema, result, path, locator, depth),
318 "map" => self.validate_map(instance, schema_obj, root_schema, result, path, locator, depth),
319 "tuple" => self.validate_tuple(instance, schema_obj, root_schema, result, path, locator, depth),
320 "choice" => self.validate_choice(instance, schema_obj, root_schema, result, path, locator, depth),
321 "any" => {} _ => {
323 result.add_error(ValidationError::instance_error(
324 InstanceErrorCode::InstanceTypeUnknown,
325 format!("Unknown type: {}", type_name),
326 path,
327 locator.get_location(path),
328 ));
329 }
330 }
331 }
332
333 fn validate_string(
336 &self,
337 instance: &Value,
338 schema_obj: &serde_json::Map<String, Value>,
339 result: &mut ValidationResult,
340 path: &str,
341 locator: &JsonSourceLocator,
342 ) {
343 let s = match instance {
344 Value::String(s) => s,
345 _ => {
346 result.add_error(ValidationError::instance_error(
347 InstanceErrorCode::InstanceStringExpected,
348 "Expected string",
349 path,
350 locator.get_location(path),
351 ));
352 return;
353 }
354 };
355
356 if self.options.extended {
357 if let Some(Value::Number(n)) = schema_obj.get("minLength") {
359 if let Some(min) = n.as_u64() {
360 if s.chars().count() < min as usize {
361 result.add_error(ValidationError::instance_error(
362 InstanceErrorCode::InstanceStringTooShort,
363 format!("String length {} is less than minimum {}", s.chars().count(), min),
364 path,
365 locator.get_location(path),
366 ));
367 }
368 }
369 }
370
371 if let Some(Value::Number(n)) = schema_obj.get("maxLength") {
373 if let Some(max) = n.as_u64() {
374 if s.chars().count() > max as usize {
375 result.add_error(ValidationError::instance_error(
376 InstanceErrorCode::InstanceStringTooLong,
377 format!("String length {} is greater than maximum {}", s.chars().count(), max),
378 path,
379 locator.get_location(path),
380 ));
381 }
382 }
383 }
384
385 if let Some(Value::String(pattern)) = schema_obj.get("pattern") {
387 if let Ok(re) = Regex::new(pattern) {
388 if !re.is_match(s) {
389 result.add_error(ValidationError::instance_error(
390 InstanceErrorCode::InstanceStringPatternMismatch,
391 format!("String does not match pattern: {}", pattern),
392 path,
393 locator.get_location(path),
394 ));
395 }
396 }
397 }
398 }
399 }
400
401 fn validate_boolean(
402 &self,
403 instance: &Value,
404 result: &mut ValidationResult,
405 path: &str,
406 locator: &JsonSourceLocator,
407 ) {
408 if !instance.is_boolean() {
409 result.add_error(ValidationError::instance_error(
410 InstanceErrorCode::InstanceBooleanExpected,
411 "Expected boolean",
412 path,
413 locator.get_location(path),
414 ));
415 }
416 }
417
418 fn validate_null(
419 &self,
420 instance: &Value,
421 result: &mut ValidationResult,
422 path: &str,
423 locator: &JsonSourceLocator,
424 ) {
425 if !instance.is_null() {
426 result.add_error(ValidationError::instance_error(
427 InstanceErrorCode::InstanceNullExpected,
428 "Expected null",
429 path,
430 locator.get_location(path),
431 ));
432 }
433 }
434
435 fn validate_number(
436 &self,
437 instance: &Value,
438 schema_obj: &serde_json::Map<String, Value>,
439 result: &mut ValidationResult,
440 path: &str,
441 locator: &JsonSourceLocator,
442 ) {
443 let num = match instance {
444 Value::Number(n) => n,
445 _ => {
446 result.add_error(ValidationError::instance_error(
447 InstanceErrorCode::InstanceNumberExpected,
448 "Expected number",
449 path,
450 locator.get_location(path),
451 ));
452 return;
453 }
454 };
455
456 if self.options.extended {
457 let value = num.as_f64().unwrap_or(0.0);
458
459 if let Some(Value::Number(n)) = schema_obj.get("minimum") {
461 if let Some(min) = n.as_f64() {
462 if value < min {
463 result.add_error(ValidationError::instance_error(
464 InstanceErrorCode::InstanceNumberTooSmall,
465 format!("Value {} is less than minimum {}", value, min),
466 path,
467 locator.get_location(path),
468 ));
469 }
470 }
471 }
472
473 if let Some(Value::Number(n)) = schema_obj.get("maximum") {
475 if let Some(max) = n.as_f64() {
476 if value > max {
477 result.add_error(ValidationError::instance_error(
478 InstanceErrorCode::InstanceNumberTooLarge,
479 format!("Value {} is greater than maximum {}", value, max),
480 path,
481 locator.get_location(path),
482 ));
483 }
484 }
485 }
486
487 if let Some(Value::Number(n)) = schema_obj.get("exclusiveMinimum") {
489 if let Some(min) = n.as_f64() {
490 if value <= min {
491 result.add_error(ValidationError::instance_error(
492 InstanceErrorCode::InstanceNumberTooSmall,
493 format!("Value {} is not greater than exclusive minimum {}", value, min),
494 path,
495 locator.get_location(path),
496 ));
497 }
498 }
499 }
500
501 if let Some(Value::Number(n)) = schema_obj.get("exclusiveMaximum") {
503 if let Some(max) = n.as_f64() {
504 if value >= max {
505 result.add_error(ValidationError::instance_error(
506 InstanceErrorCode::InstanceNumberTooLarge,
507 format!("Value {} is not less than exclusive maximum {}", value, max),
508 path,
509 locator.get_location(path),
510 ));
511 }
512 }
513 }
514 }
515 }
516
517 fn validate_int32(
518 &self,
519 instance: &Value,
520 _schema_obj: &serde_json::Map<String, Value>,
521 result: &mut ValidationResult,
522 path: &str,
523 locator: &JsonSourceLocator,
524 ) {
525 self.validate_int_range(instance, _schema_obj, result, path, locator, i32::MIN as i64, i32::MAX as i64, "int32")
526 }
527
528 fn validate_int_range(
529 &self,
530 instance: &Value,
531 schema_obj: &serde_json::Map<String, Value>,
532 result: &mut ValidationResult,
533 path: &str,
534 locator: &JsonSourceLocator,
535 min: i64,
536 max: i64,
537 type_name: &str,
538 ) {
539 let num = match instance {
540 Value::Number(n) => n,
541 _ => {
542 result.add_error(ValidationError::instance_error(
543 InstanceErrorCode::InstanceIntegerExpected,
544 format!("Expected {}", type_name),
545 path,
546 locator.get_location(path),
547 ));
548 return;
549 }
550 };
551
552 let val = if let Some(v) = num.as_i64() {
553 if v < min || v > max {
554 result.add_error(ValidationError::instance_error(
555 InstanceErrorCode::InstanceIntegerOutOfRange,
556 format!("Value {} is out of range for {} ({} to {})", v, type_name, min, max),
557 path,
558 locator.get_location(path),
559 ));
560 }
561 v as f64
562 } else if let Some(v) = num.as_f64() {
563 if v.fract() != 0.0 {
564 result.add_error(ValidationError::instance_error(
565 InstanceErrorCode::InstanceIntegerExpected,
566 format!("Expected integer, got {}", v),
567 path,
568 locator.get_location(path),
569 ));
570 return;
571 }
572 if v < min as f64 || v > max as f64 {
573 result.add_error(ValidationError::instance_error(
574 InstanceErrorCode::InstanceIntegerOutOfRange,
575 format!("Value {} is out of range for {}", v, type_name),
576 path,
577 locator.get_location(path),
578 ));
579 }
580 v
581 } else {
582 return;
583 };
584
585 if self.options.extended {
587 if let Some(Value::Number(n)) = schema_obj.get("minimum") {
589 if let Some(min_val) = n.as_f64() {
590 if val < min_val {
591 result.add_error(ValidationError::instance_error(
592 InstanceErrorCode::InstanceNumberTooSmall,
593 format!("Value {} is less than minimum {}", val, min_val),
594 path,
595 locator.get_location(path),
596 ));
597 }
598 }
599 }
600
601 if let Some(Value::Number(n)) = schema_obj.get("maximum") {
603 if let Some(max_val) = n.as_f64() {
604 if val > max_val {
605 result.add_error(ValidationError::instance_error(
606 InstanceErrorCode::InstanceNumberTooLarge,
607 format!("Value {} is greater than maximum {}", val, max_val),
608 path,
609 locator.get_location(path),
610 ));
611 }
612 }
613 }
614
615 if let Some(Value::Number(n)) = schema_obj.get("exclusiveMinimum") {
617 if let Some(min_val) = n.as_f64() {
618 if val <= min_val {
619 result.add_error(ValidationError::instance_error(
620 InstanceErrorCode::InstanceNumberTooSmall,
621 format!("Value {} is not greater than exclusive minimum {}", val, min_val),
622 path,
623 locator.get_location(path),
624 ));
625 }
626 }
627 }
628
629 if let Some(Value::Number(n)) = schema_obj.get("exclusiveMaximum") {
631 if let Some(max_val) = n.as_f64() {
632 if val >= max_val {
633 result.add_error(ValidationError::instance_error(
634 InstanceErrorCode::InstanceNumberTooLarge,
635 format!("Value {} is not less than exclusive maximum {}", val, max_val),
636 path,
637 locator.get_location(path),
638 ));
639 }
640 }
641 }
642
643 if let Some(Value::Number(n)) = schema_obj.get("multipleOf") {
645 if let Some(mul) = n.as_f64() {
646 if mul > 0.0 && (val % mul).abs() > f64::EPSILON {
647 result.add_error(ValidationError::instance_error(
648 InstanceErrorCode::InstanceNumberNotMultiple,
649 format!("Value {} is not a multiple of {}", val, mul),
650 path,
651 locator.get_location(path),
652 ));
653 }
654 }
655 }
656 }
657 }
658
659 fn validate_uint_range(
660 &self,
661 instance: &Value,
662 schema_obj: &serde_json::Map<String, Value>,
663 result: &mut ValidationResult,
664 path: &str,
665 locator: &JsonSourceLocator,
666 min: u64,
667 max: u64,
668 type_name: &str,
669 ) {
670 let num = match instance {
671 Value::Number(n) => n,
672 _ => {
673 result.add_error(ValidationError::instance_error(
674 InstanceErrorCode::InstanceIntegerExpected,
675 format!("Expected {}", type_name),
676 path,
677 locator.get_location(path),
678 ));
679 return;
680 }
681 };
682
683 if let Some(val) = num.as_u64() {
684 if val < min || val > max {
685 result.add_error(ValidationError::instance_error(
686 InstanceErrorCode::InstanceIntegerOutOfRange,
687 format!("Value {} is out of range for {} ({} to {})", val, type_name, min, max),
688 path,
689 locator.get_location(path),
690 ));
691 }
692 } else if let Some(v) = num.as_i64() {
693 if v < 0 {
694 result.add_error(ValidationError::instance_error(
695 InstanceErrorCode::InstanceIntegerOutOfRange,
696 format!("Value {} is negative, expected unsigned {}", v, type_name),
697 path,
698 locator.get_location(path),
699 ));
700 return;
701 }
702 } else {
703 return;
704 }
705
706 let val = num.as_f64().unwrap_or(0.0);
708
709 if self.options.extended {
711 if let Some(Value::Number(n)) = schema_obj.get("minimum") {
713 if let Some(min_val) = n.as_f64() {
714 if val < min_val {
715 result.add_error(ValidationError::instance_error(
716 InstanceErrorCode::InstanceNumberTooSmall,
717 format!("Value {} is less than minimum {}", val, min_val),
718 path,
719 locator.get_location(path),
720 ));
721 }
722 }
723 }
724
725 if let Some(Value::Number(n)) = schema_obj.get("maximum") {
727 if let Some(max_val) = n.as_f64() {
728 if val > max_val {
729 result.add_error(ValidationError::instance_error(
730 InstanceErrorCode::InstanceNumberTooLarge,
731 format!("Value {} is greater than maximum {}", val, max_val),
732 path,
733 locator.get_location(path),
734 ));
735 }
736 }
737 }
738
739 if let Some(Value::Number(n)) = schema_obj.get("exclusiveMinimum") {
741 if let Some(min_val) = n.as_f64() {
742 if val <= min_val {
743 result.add_error(ValidationError::instance_error(
744 InstanceErrorCode::InstanceNumberTooSmall,
745 format!("Value {} is not greater than exclusive minimum {}", val, min_val),
746 path,
747 locator.get_location(path),
748 ));
749 }
750 }
751 }
752
753 if let Some(Value::Number(n)) = schema_obj.get("exclusiveMaximum") {
755 if let Some(max_val) = n.as_f64() {
756 if val >= max_val {
757 result.add_error(ValidationError::instance_error(
758 InstanceErrorCode::InstanceNumberTooLarge,
759 format!("Value {} is not less than exclusive maximum {}", val, max_val),
760 path,
761 locator.get_location(path),
762 ));
763 }
764 }
765 }
766
767 if let Some(Value::Number(n)) = schema_obj.get("multipleOf") {
769 if let Some(mul) = n.as_f64() {
770 if mul > 0.0 && (val % mul).abs() > f64::EPSILON {
771 result.add_error(ValidationError::instance_error(
772 InstanceErrorCode::InstanceNumberNotMultiple,
773 format!("Value {} is not a multiple of {}", val, mul),
774 path,
775 locator.get_location(path),
776 ));
777 }
778 }
779 }
780 }
781 }
782
783 fn validate_int64(
784 &self,
785 instance: &Value,
786 _schema_obj: &serde_json::Map<String, Value>,
787 result: &mut ValidationResult,
788 path: &str,
789 locator: &JsonSourceLocator,
790 ) {
791 match instance {
792 Value::Number(n) => {
793 if n.as_i64().is_none() && n.as_f64().is_none_or(|f| f.fract() != 0.0) {
794 result.add_error(ValidationError::instance_error(
795 InstanceErrorCode::InstanceIntegerExpected,
796 "Expected int64",
797 path,
798 locator.get_location(path),
799 ));
800 }
801 }
802 Value::String(s) => {
803 if s.parse::<i64>().is_err() {
805 result.add_error(ValidationError::instance_error(
806 InstanceErrorCode::InstanceIntegerExpected,
807 "Expected int64 (as number or string)",
808 path,
809 locator.get_location(path),
810 ));
811 }
812 }
813 _ => {
814 result.add_error(ValidationError::instance_error(
815 InstanceErrorCode::InstanceIntegerExpected,
816 "Expected int64",
817 path,
818 locator.get_location(path),
819 ));
820 }
821 }
822 }
823
824 fn validate_int128(
825 &self,
826 instance: &Value,
827 _schema_obj: &serde_json::Map<String, Value>,
828 result: &mut ValidationResult,
829 path: &str,
830 locator: &JsonSourceLocator,
831 ) {
832 match instance {
833 Value::Number(_) => {} Value::String(s) => {
835 if s.parse::<i128>().is_err() {
836 result.add_error(ValidationError::instance_error(
837 InstanceErrorCode::InstanceIntegerExpected,
838 "Expected int128 (as number or string)",
839 path,
840 locator.get_location(path),
841 ));
842 }
843 }
844 _ => {
845 result.add_error(ValidationError::instance_error(
846 InstanceErrorCode::InstanceIntegerExpected,
847 "Expected int128",
848 path,
849 locator.get_location(path),
850 ));
851 }
852 }
853 }
854
855 fn validate_uint32(
856 &self,
857 instance: &Value,
858 _schema_obj: &serde_json::Map<String, Value>,
859 result: &mut ValidationResult,
860 path: &str,
861 locator: &JsonSourceLocator,
862 ) {
863 self.validate_uint_range(instance, _schema_obj, result, path, locator, 0, u32::MAX as u64, "uint32")
864 }
865
866 fn validate_uint64(
867 &self,
868 instance: &Value,
869 _schema_obj: &serde_json::Map<String, Value>,
870 result: &mut ValidationResult,
871 path: &str,
872 locator: &JsonSourceLocator,
873 ) {
874 match instance {
875 Value::Number(n) => {
876 if n.as_u64().is_none() {
877 if let Some(i) = n.as_i64() {
878 if i < 0 {
879 result.add_error(ValidationError::instance_error(
880 InstanceErrorCode::InstanceIntegerOutOfRange,
881 "Expected unsigned uint64",
882 path,
883 locator.get_location(path),
884 ));
885 }
886 }
887 }
888 }
889 Value::String(s) => {
890 if s.parse::<u64>().is_err() {
891 result.add_error(ValidationError::instance_error(
892 InstanceErrorCode::InstanceIntegerExpected,
893 "Expected uint64 (as number or string)",
894 path,
895 locator.get_location(path),
896 ));
897 }
898 }
899 _ => {
900 result.add_error(ValidationError::instance_error(
901 InstanceErrorCode::InstanceIntegerExpected,
902 "Expected uint64",
903 path,
904 locator.get_location(path),
905 ));
906 }
907 }
908 }
909
910 fn validate_uint128(
911 &self,
912 instance: &Value,
913 _schema_obj: &serde_json::Map<String, Value>,
914 result: &mut ValidationResult,
915 path: &str,
916 locator: &JsonSourceLocator,
917 ) {
918 match instance {
919 Value::Number(n) => {
920 if let Some(i) = n.as_i64() {
921 if i < 0 {
922 result.add_error(ValidationError::instance_error(
923 InstanceErrorCode::InstanceIntegerOutOfRange,
924 "Expected unsigned uint128",
925 path,
926 locator.get_location(path),
927 ));
928 }
929 }
930 }
931 Value::String(s) => {
932 if s.parse::<u128>().is_err() {
933 result.add_error(ValidationError::instance_error(
934 InstanceErrorCode::InstanceIntegerExpected,
935 "Expected uint128 (as number or string)",
936 path,
937 locator.get_location(path),
938 ));
939 }
940 }
941 _ => {
942 result.add_error(ValidationError::instance_error(
943 InstanceErrorCode::InstanceIntegerExpected,
944 "Expected uint128",
945 path,
946 locator.get_location(path),
947 ));
948 }
949 }
950 }
951
952 fn validate_date(
955 &self,
956 instance: &Value,
957 result: &mut ValidationResult,
958 path: &str,
959 locator: &JsonSourceLocator,
960 ) {
961 let s = match instance {
962 Value::String(s) => s,
963 _ => {
964 result.add_error(ValidationError::instance_error(
965 InstanceErrorCode::InstanceDateExpected,
966 "Expected date string",
967 path,
968 locator.get_location(path),
969 ));
970 return;
971 }
972 };
973
974 if NaiveDate::parse_from_str(s, "%Y-%m-%d").is_err() {
975 result.add_error(ValidationError::instance_error(
976 InstanceErrorCode::InstanceDateInvalid,
977 format!("Invalid date format: {}", s),
978 path,
979 locator.get_location(path),
980 ));
981 }
982 }
983
984 fn validate_time(
985 &self,
986 instance: &Value,
987 result: &mut ValidationResult,
988 path: &str,
989 locator: &JsonSourceLocator,
990 ) {
991 let s = match instance {
992 Value::String(s) => s,
993 _ => {
994 result.add_error(ValidationError::instance_error(
995 InstanceErrorCode::InstanceTimeExpected,
996 "Expected time string",
997 path,
998 locator.get_location(path),
999 ));
1000 return;
1001 }
1002 };
1003
1004 let valid = NaiveTime::parse_from_str(s, "%H:%M:%S").is_ok()
1006 || NaiveTime::parse_from_str(s, "%H:%M:%S%.f").is_ok()
1007 || NaiveTime::parse_from_str(s, "%H:%M").is_ok();
1008
1009 if !valid {
1010 result.add_error(ValidationError::instance_error(
1011 InstanceErrorCode::InstanceTimeInvalid,
1012 format!("Invalid time format: {}", s),
1013 path,
1014 locator.get_location(path),
1015 ));
1016 }
1017 }
1018
1019 fn validate_datetime(
1020 &self,
1021 instance: &Value,
1022 result: &mut ValidationResult,
1023 path: &str,
1024 locator: &JsonSourceLocator,
1025 ) {
1026 let s = match instance {
1027 Value::String(s) => s,
1028 _ => {
1029 result.add_error(ValidationError::instance_error(
1030 InstanceErrorCode::InstanceDateTimeExpected,
1031 "Expected datetime string",
1032 path,
1033 locator.get_location(path),
1034 ));
1035 return;
1036 }
1037 };
1038
1039 if DateTime::parse_from_rfc3339(s).is_err() {
1041 result.add_error(ValidationError::instance_error(
1042 InstanceErrorCode::InstanceDateTimeInvalid,
1043 format!("Invalid datetime format (expected RFC 3339): {}", s),
1044 path,
1045 locator.get_location(path),
1046 ));
1047 }
1048 }
1049
1050 fn validate_duration(
1051 &self,
1052 instance: &Value,
1053 result: &mut ValidationResult,
1054 path: &str,
1055 locator: &JsonSourceLocator,
1056 ) {
1057 let s = match instance {
1058 Value::String(s) => s,
1059 _ => {
1060 result.add_error(ValidationError::instance_error(
1061 InstanceErrorCode::InstanceDurationExpected,
1062 "Expected duration string",
1063 path,
1064 locator.get_location(path),
1065 ));
1066 return;
1067 }
1068 };
1069
1070 let duration_pattern = Regex::new(r"^P(\d+Y)?(\d+M)?(\d+W)?(\d+D)?(T(\d+H)?(\d+M)?(\d+(\.\d+)?S)?)?$").unwrap();
1072 if !duration_pattern.is_match(s) {
1073 result.add_error(ValidationError::instance_error(
1074 InstanceErrorCode::InstanceDurationInvalid,
1075 format!("Invalid duration format (expected ISO 8601): {}", s),
1076 path,
1077 locator.get_location(path),
1078 ));
1079 }
1080 }
1081
1082 fn validate_uuid(
1085 &self,
1086 instance: &Value,
1087 result: &mut ValidationResult,
1088 path: &str,
1089 locator: &JsonSourceLocator,
1090 ) {
1091 let s = match instance {
1092 Value::String(s) => s,
1093 _ => {
1094 result.add_error(ValidationError::instance_error(
1095 InstanceErrorCode::InstanceUuidExpected,
1096 "Expected UUID string",
1097 path,
1098 locator.get_location(path),
1099 ));
1100 return;
1101 }
1102 };
1103
1104 if Uuid::parse_str(s).is_err() {
1105 result.add_error(ValidationError::instance_error(
1106 InstanceErrorCode::InstanceUuidInvalid,
1107 format!("Invalid UUID format: {}", s),
1108 path,
1109 locator.get_location(path),
1110 ));
1111 }
1112 }
1113
1114 fn validate_uri(
1115 &self,
1116 instance: &Value,
1117 result: &mut ValidationResult,
1118 path: &str,
1119 locator: &JsonSourceLocator,
1120 ) {
1121 let s = match instance {
1122 Value::String(s) => s,
1123 _ => {
1124 result.add_error(ValidationError::instance_error(
1125 InstanceErrorCode::InstanceUriExpected,
1126 "Expected URI string",
1127 path,
1128 locator.get_location(path),
1129 ));
1130 return;
1131 }
1132 };
1133
1134 if url::Url::parse(s).is_err() {
1135 result.add_error(ValidationError::instance_error(
1136 InstanceErrorCode::InstanceUriInvalid,
1137 format!("Invalid URI format: {}", s),
1138 path,
1139 locator.get_location(path),
1140 ));
1141 }
1142 }
1143
1144 fn validate_binary(
1145 &self,
1146 instance: &Value,
1147 result: &mut ValidationResult,
1148 path: &str,
1149 locator: &JsonSourceLocator,
1150 ) {
1151 let s = match instance {
1152 Value::String(s) => s,
1153 _ => {
1154 result.add_error(ValidationError::instance_error(
1155 InstanceErrorCode::InstanceBinaryExpected,
1156 "Expected base64 string",
1157 path,
1158 locator.get_location(path),
1159 ));
1160 return;
1161 }
1162 };
1163
1164 if base64::engine::general_purpose::STANDARD.decode(s).is_err() {
1165 result.add_error(ValidationError::instance_error(
1166 InstanceErrorCode::InstanceBinaryInvalid,
1167 format!("Invalid base64 encoding: {}", s),
1168 path,
1169 locator.get_location(path),
1170 ));
1171 }
1172 }
1173
1174 fn validate_jsonpointer(
1175 &self,
1176 instance: &Value,
1177 result: &mut ValidationResult,
1178 path: &str,
1179 locator: &JsonSourceLocator,
1180 ) {
1181 let s = match instance {
1182 Value::String(s) => s,
1183 _ => {
1184 result.add_error(ValidationError::instance_error(
1185 InstanceErrorCode::InstanceJsonPointerExpected,
1186 "Expected JSON Pointer string",
1187 path,
1188 locator.get_location(path),
1189 ));
1190 return;
1191 }
1192 };
1193
1194 if !s.is_empty() && !s.starts_with('/') {
1196 result.add_error(ValidationError::instance_error(
1197 InstanceErrorCode::InstanceJsonPointerInvalid,
1198 format!("Invalid JSON Pointer format: {}", s),
1199 path,
1200 locator.get_location(path),
1201 ));
1202 }
1203 }
1204
1205 fn validate_object(
1208 &self,
1209 instance: &Value,
1210 schema_obj: &serde_json::Map<String, Value>,
1211 root_schema: &Value,
1212 result: &mut ValidationResult,
1213 path: &str,
1214 locator: &JsonSourceLocator,
1215 depth: usize,
1216 ) {
1217 let obj = match instance {
1218 Value::Object(o) => o,
1219 _ => {
1220 result.add_error(ValidationError::instance_error(
1221 InstanceErrorCode::InstanceObjectExpected,
1222 "Expected object",
1223 path,
1224 locator.get_location(path),
1225 ));
1226 return;
1227 }
1228 };
1229
1230 let properties = schema_obj.get("properties").and_then(Value::as_object);
1231 let required = schema_obj.get("required").and_then(Value::as_array);
1232 let additional_properties = schema_obj.get("additionalProperties");
1233
1234 if let Some(req) = required {
1236 for item in req {
1237 if let Value::String(prop_name) = item {
1238 if !obj.contains_key(prop_name) {
1239 result.add_error(ValidationError::instance_error(
1240 InstanceErrorCode::InstanceRequiredMissing,
1241 format!("Required property missing: {}", prop_name),
1242 path,
1243 locator.get_location(path),
1244 ));
1245 }
1246 }
1247 }
1248 }
1249
1250 for (prop_name, prop_value) in obj {
1252 let prop_path = format!("{}/{}", path, prop_name);
1253
1254 if let Some(props) = properties {
1255 if let Some(prop_schema) = props.get(prop_name) {
1256 self.validate_instance(prop_value, prop_schema, root_schema, result, &prop_path, locator, depth + 1);
1257 } else {
1258 match additional_properties {
1260 Some(Value::Bool(false)) => {
1261 result.add_error(ValidationError::instance_error(
1262 InstanceErrorCode::InstanceAdditionalProperty,
1263 format!("Additional property not allowed: {}", prop_name),
1264 &prop_path,
1265 locator.get_location(&prop_path),
1266 ));
1267 }
1268 Some(Value::Object(_)) => {
1269 self.validate_instance(prop_value, additional_properties.unwrap(), root_schema, result, &prop_path, locator, depth + 1);
1270 }
1271 _ => {}
1272 }
1273 }
1274 }
1275 }
1276
1277 if self.options.extended {
1279 if let Some(Value::Number(n)) = schema_obj.get("minProperties") {
1281 if let Some(min) = n.as_u64() {
1282 if obj.len() < min as usize {
1283 result.add_error(ValidationError::instance_error(
1284 InstanceErrorCode::InstanceTooFewProperties,
1285 format!("Object has {} properties, minimum is {}", obj.len(), min),
1286 path,
1287 locator.get_location(path),
1288 ));
1289 }
1290 }
1291 }
1292
1293 if let Some(Value::Number(n)) = schema_obj.get("maxProperties") {
1295 if let Some(max) = n.as_u64() {
1296 if obj.len() > max as usize {
1297 result.add_error(ValidationError::instance_error(
1298 InstanceErrorCode::InstanceTooManyProperties,
1299 format!("Object has {} properties, maximum is {}", obj.len(), max),
1300 path,
1301 locator.get_location(path),
1302 ));
1303 }
1304 }
1305 }
1306
1307 if let Some(Value::Object(deps)) = schema_obj.get("dependentRequired") {
1309 for (prop, required_props) in deps {
1310 if obj.contains_key(prop) {
1311 if let Value::Array(req) = required_props {
1312 for req_prop in req {
1313 if let Value::String(req_name) = req_prop {
1314 if !obj.contains_key(req_name) {
1315 result.add_error(ValidationError::instance_error(
1316 InstanceErrorCode::InstanceDependentRequiredMissing,
1317 format!("Property '{}' requires '{}' to be present", prop, req_name),
1318 path,
1319 locator.get_location(path),
1320 ));
1321 }
1322 }
1323 }
1324 }
1325 }
1326 }
1327 }
1328 }
1329 }
1330
1331 fn validate_array(
1332 &self,
1333 instance: &Value,
1334 schema_obj: &serde_json::Map<String, Value>,
1335 root_schema: &Value,
1336 result: &mut ValidationResult,
1337 path: &str,
1338 locator: &JsonSourceLocator,
1339 depth: usize,
1340 ) {
1341 let arr = match instance {
1342 Value::Array(a) => a,
1343 _ => {
1344 result.add_error(ValidationError::instance_error(
1345 InstanceErrorCode::InstanceArrayExpected,
1346 "Expected array",
1347 path,
1348 locator.get_location(path),
1349 ));
1350 return;
1351 }
1352 };
1353
1354 if let Some(items_schema) = schema_obj.get("items") {
1356 for (i, item) in arr.iter().enumerate() {
1357 let item_path = format!("{}/{}", path, i);
1358 self.validate_instance(item, items_schema, root_schema, result, &item_path, locator, depth + 1);
1359 }
1360 }
1361
1362 if self.options.extended {
1363 if let Some(Value::Number(n)) = schema_obj.get("minItems") {
1365 if let Some(min) = n.as_u64() {
1366 if arr.len() < min as usize {
1367 result.add_error(ValidationError::instance_error(
1368 InstanceErrorCode::InstanceArrayTooShort,
1369 format!("Array length {} is less than minimum {}", arr.len(), min),
1370 path,
1371 locator.get_location(path),
1372 ));
1373 }
1374 }
1375 }
1376
1377 if let Some(Value::Number(n)) = schema_obj.get("maxItems") {
1379 if let Some(max) = n.as_u64() {
1380 if arr.len() > max as usize {
1381 result.add_error(ValidationError::instance_error(
1382 InstanceErrorCode::InstanceArrayTooLong,
1383 format!("Array length {} is greater than maximum {}", arr.len(), max),
1384 path,
1385 locator.get_location(path),
1386 ));
1387 }
1388 }
1389 }
1390
1391 if let Some(contains_schema) = schema_obj.get("contains") {
1393 let mut match_count = 0;
1394 for item in arr.iter() {
1395 let mut temp_result = ValidationResult::new();
1396 self.validate_instance(item, contains_schema, root_schema, &mut temp_result, path, locator, depth + 1);
1397 if temp_result.is_valid() {
1398 match_count += 1;
1399 }
1400 }
1401
1402 let min_contains = schema_obj.get("minContains")
1403 .and_then(Value::as_u64)
1404 .unwrap_or(1);
1405 let max_contains = schema_obj.get("maxContains")
1406 .and_then(Value::as_u64);
1407
1408 if match_count < min_contains as usize {
1409 result.add_error(ValidationError::instance_error(
1410 InstanceErrorCode::InstanceArrayContainsTooFew,
1411 format!("Array contains {} matching items, minimum is {}", match_count, min_contains),
1412 path,
1413 locator.get_location(path),
1414 ));
1415 }
1416
1417 if let Some(max) = max_contains {
1418 if match_count > max as usize {
1419 result.add_error(ValidationError::instance_error(
1420 InstanceErrorCode::InstanceArrayContainsTooMany,
1421 format!("Array contains {} matching items, maximum is {}", match_count, max),
1422 path,
1423 locator.get_location(path),
1424 ));
1425 }
1426 }
1427 }
1428 }
1429 }
1430
1431 fn validate_set(
1432 &self,
1433 instance: &Value,
1434 schema_obj: &serde_json::Map<String, Value>,
1435 root_schema: &Value,
1436 result: &mut ValidationResult,
1437 path: &str,
1438 locator: &JsonSourceLocator,
1439 depth: usize,
1440 ) {
1441 let arr = match instance {
1442 Value::Array(a) => a,
1443 _ => {
1444 result.add_error(ValidationError::instance_error(
1445 InstanceErrorCode::InstanceSetExpected,
1446 "Expected array (set)",
1447 path,
1448 locator.get_location(path),
1449 ));
1450 return;
1451 }
1452 };
1453
1454 let mut seen = Vec::new();
1456 for (i, item) in arr.iter().enumerate() {
1457 let item_str = item.to_string();
1458 if seen.contains(&item_str) {
1459 result.add_error(ValidationError::instance_error(
1460 InstanceErrorCode::InstanceSetNotUnique,
1461 "Set contains duplicate values",
1462 &format!("{}/{}", path, i),
1463 locator.get_location(&format!("{}/{}", path, i)),
1464 ));
1465 } else {
1466 seen.push(item_str);
1467 }
1468 }
1469
1470 if let Some(items_schema) = schema_obj.get("items") {
1472 for (i, item) in arr.iter().enumerate() {
1473 let item_path = format!("{}/{}", path, i);
1474 self.validate_instance(item, items_schema, root_schema, result, &item_path, locator, depth + 1);
1475 }
1476 }
1477 }
1478
1479 fn validate_map(
1480 &self,
1481 instance: &Value,
1482 schema_obj: &serde_json::Map<String, Value>,
1483 root_schema: &Value,
1484 result: &mut ValidationResult,
1485 path: &str,
1486 locator: &JsonSourceLocator,
1487 depth: usize,
1488 ) {
1489 let obj = match instance {
1490 Value::Object(o) => o,
1491 _ => {
1492 result.add_error(ValidationError::instance_error(
1493 InstanceErrorCode::InstanceMapExpected,
1494 "Expected object (map)",
1495 path,
1496 locator.get_location(path),
1497 ));
1498 return;
1499 }
1500 };
1501
1502 if let Some(values_schema) = schema_obj.get("values") {
1504 for (key, value) in obj.iter() {
1505 let value_path = format!("{}/{}", path, key);
1506 self.validate_instance(value, values_schema, root_schema, result, &value_path, locator, depth + 1);
1507 }
1508 }
1509
1510 if self.options.extended {
1512 if let Some(Value::Number(n)) = schema_obj.get("minEntries") {
1514 if let Some(min) = n.as_u64() {
1515 if obj.len() < min as usize {
1516 result.add_error(ValidationError::instance_error(
1517 InstanceErrorCode::InstanceMapTooFewEntries,
1518 format!("Map has {} entries, minimum is {}", obj.len(), min),
1519 path,
1520 locator.get_location(path),
1521 ));
1522 }
1523 }
1524 }
1525
1526 if let Some(Value::Number(n)) = schema_obj.get("maxEntries") {
1528 if let Some(max) = n.as_u64() {
1529 if obj.len() > max as usize {
1530 result.add_error(ValidationError::instance_error(
1531 InstanceErrorCode::InstanceMapTooManyEntries,
1532 format!("Map has {} entries, maximum is {}", obj.len(), max),
1533 path,
1534 locator.get_location(path),
1535 ));
1536 }
1537 }
1538 }
1539
1540 if let Some(keynames_schema) = schema_obj.get("keyNames") {
1542 if let Some(keynames_obj) = keynames_schema.as_object() {
1543 if let Some(Value::String(pattern)) = keynames_obj.get("pattern") {
1544 if let Ok(re) = Regex::new(pattern) {
1545 for key in obj.keys() {
1546 if !re.is_match(key) {
1547 result.add_error(ValidationError::instance_error(
1548 InstanceErrorCode::InstanceMapKeyPatternMismatch,
1549 format!("Map key '{}' does not match pattern '{}'", key, pattern),
1550 path,
1551 locator.get_location(path),
1552 ));
1553 }
1554 }
1555 }
1556 }
1557 }
1558 }
1559 }
1560 }
1561
1562 fn validate_tuple(
1563 &self,
1564 instance: &Value,
1565 schema_obj: &serde_json::Map<String, Value>,
1566 root_schema: &Value,
1567 result: &mut ValidationResult,
1568 path: &str,
1569 locator: &JsonSourceLocator,
1570 depth: usize,
1571 ) {
1572 let arr = match instance {
1573 Value::Array(a) => a,
1574 _ => {
1575 result.add_error(ValidationError::instance_error(
1576 InstanceErrorCode::InstanceTupleExpected,
1577 "Expected array (tuple)",
1578 path,
1579 locator.get_location(path),
1580 ));
1581 return;
1582 }
1583 };
1584
1585 let properties = schema_obj.get("properties").and_then(Value::as_object);
1586 let tuple_order = schema_obj.get("tuple").and_then(Value::as_array);
1587
1588 if let (Some(props), Some(order)) = (properties, tuple_order) {
1589 if arr.len() != order.len() {
1591 result.add_error(ValidationError::instance_error(
1592 InstanceErrorCode::InstanceTupleLengthMismatch,
1593 format!("Tuple length {} does not match expected {}", arr.len(), order.len()),
1594 path,
1595 locator.get_location(path),
1596 ));
1597 return;
1598 }
1599
1600 for (i, prop_name_val) in order.iter().enumerate() {
1602 if let Value::String(prop_name) = prop_name_val {
1603 if let Some(prop_schema) = props.get(prop_name) {
1604 let elem_path = format!("{}/{}", path, i);
1605 self.validate_instance(&arr[i], prop_schema, root_schema, result, &elem_path, locator, depth + 1);
1606 }
1607 }
1608 }
1609 }
1610 }
1611
1612 fn validate_choice(
1613 &self,
1614 instance: &Value,
1615 schema_obj: &serde_json::Map<String, Value>,
1616 root_schema: &Value,
1617 result: &mut ValidationResult,
1618 path: &str,
1619 locator: &JsonSourceLocator,
1620 depth: usize,
1621 ) {
1622 let choices = match schema_obj.get("choices").and_then(Value::as_object) {
1623 Some(c) => c,
1624 None => return,
1625 };
1626
1627 let selector = schema_obj.get("selector").and_then(Value::as_str);
1628
1629 if let Some(selector_prop) = selector {
1630 let obj = match instance {
1632 Value::Object(o) => o,
1633 _ => {
1634 result.add_error(ValidationError::instance_error(
1635 InstanceErrorCode::InstanceObjectExpected,
1636 "Choice with selector expects object",
1637 path,
1638 locator.get_location(path),
1639 ));
1640 return;
1641 }
1642 };
1643
1644 let selector_value = match obj.get(selector_prop) {
1645 Some(Value::String(s)) => s.as_str(),
1646 Some(_) => {
1647 result.add_error(ValidationError::instance_error(
1648 InstanceErrorCode::InstanceChoiceSelectorInvalid,
1649 format!("Selector property '{}' must be a string", selector_prop),
1650 &format!("{}/{}", path, selector_prop),
1651 locator.get_location(&format!("{}/{}", path, selector_prop)),
1652 ));
1653 return;
1654 }
1655 None => {
1656 result.add_error(ValidationError::instance_error(
1657 InstanceErrorCode::InstanceChoiceSelectorMissing,
1658 format!("Missing selector property: {}", selector_prop),
1659 path,
1660 locator.get_location(path),
1661 ));
1662 return;
1663 }
1664 };
1665
1666 if let Some(choice_schema) = choices.get(selector_value) {
1667 self.validate_instance(instance, choice_schema, root_schema, result, path, locator, depth + 1);
1668 } else {
1669 result.add_error(ValidationError::instance_error(
1670 InstanceErrorCode::InstanceChoiceUnknown,
1671 format!("Unknown choice: {}", selector_value),
1672 path,
1673 locator.get_location(path),
1674 ));
1675 }
1676 } else {
1677 let mut match_count = 0;
1679
1680 for (_choice_name, choice_schema) in choices {
1681 let mut choice_result = ValidationResult::new();
1682 self.validate_instance(instance, choice_schema, root_schema, &mut choice_result, path, locator, depth + 1);
1683 if choice_result.is_valid() {
1684 match_count += 1;
1685 }
1686 }
1687
1688 if match_count == 0 {
1689 result.add_error(ValidationError::instance_error(
1690 InstanceErrorCode::InstanceChoiceNoMatch,
1691 "Value does not match any choice option",
1692 path,
1693 locator.get_location(path),
1694 ));
1695 } else if match_count > 1 {
1696 result.add_error(ValidationError::instance_error(
1697 InstanceErrorCode::InstanceChoiceMultipleMatches,
1698 format!("Value matches {} choice options (should match exactly one)", match_count),
1699 path,
1700 locator.get_location(path),
1701 ));
1702 }
1703 }
1704 }
1705
1706 fn validate_composition(
1709 &self,
1710 instance: &Value,
1711 schema_obj: &serde_json::Map<String, Value>,
1712 root_schema: &Value,
1713 result: &mut ValidationResult,
1714 path: &str,
1715 locator: &JsonSourceLocator,
1716 depth: usize,
1717 ) {
1718 if let Some(Value::Array(schemas)) = schema_obj.get("allOf") {
1720 for schema in schemas {
1721 let mut sub_result = ValidationResult::new();
1722 self.validate_instance(instance, schema, root_schema, &mut sub_result, path, locator, depth + 1);
1723 if !sub_result.is_valid() {
1724 result.add_error(ValidationError::instance_error(
1725 InstanceErrorCode::InstanceAllOfFailed,
1726 "Value does not match all schemas in allOf",
1727 path,
1728 locator.get_location(path),
1729 ));
1730 result.add_errors(sub_result.all_errors().iter().cloned());
1731 return;
1732 }
1733 }
1734 }
1735
1736 if let Some(Value::Array(schemas)) = schema_obj.get("anyOf") {
1738 let mut any_valid = false;
1739 for schema in schemas {
1740 let mut sub_result = ValidationResult::new();
1741 self.validate_instance(instance, schema, root_schema, &mut sub_result, path, locator, depth + 1);
1742 if sub_result.is_valid() {
1743 any_valid = true;
1744 break;
1745 }
1746 }
1747 if !any_valid {
1748 result.add_error(ValidationError::instance_error(
1749 InstanceErrorCode::InstanceAnyOfFailed,
1750 "Value does not match any schema in anyOf",
1751 path,
1752 locator.get_location(path),
1753 ));
1754 }
1755 }
1756
1757 if let Some(Value::Array(schemas)) = schema_obj.get("oneOf") {
1759 let mut match_count = 0;
1760 for schema in schemas {
1761 let mut sub_result = ValidationResult::new();
1762 self.validate_instance(instance, schema, root_schema, &mut sub_result, path, locator, depth + 1);
1763 if sub_result.is_valid() {
1764 match_count += 1;
1765 }
1766 }
1767 if match_count == 0 {
1768 result.add_error(ValidationError::instance_error(
1769 InstanceErrorCode::InstanceOneOfFailed,
1770 "Value does not match any schema in oneOf",
1771 path,
1772 locator.get_location(path),
1773 ));
1774 } else if match_count > 1 {
1775 result.add_error(ValidationError::instance_error(
1776 InstanceErrorCode::InstanceOneOfMultiple,
1777 format!("Value matches {} schemas in oneOf (should match exactly one)", match_count),
1778 path,
1779 locator.get_location(path),
1780 ));
1781 }
1782 }
1783
1784 if let Some(not_schema) = schema_obj.get("not") {
1786 let mut sub_result = ValidationResult::new();
1787 self.validate_instance(instance, not_schema, root_schema, &mut sub_result, path, locator, depth + 1);
1788 if sub_result.is_valid() {
1789 result.add_error(ValidationError::instance_error(
1790 InstanceErrorCode::InstanceNotFailed,
1791 "Value should not match the schema in 'not'",
1792 path,
1793 locator.get_location(path),
1794 ));
1795 }
1796 }
1797
1798 if let Some(if_schema) = schema_obj.get("if") {
1800 let mut if_result = ValidationResult::new();
1801 self.validate_instance(instance, if_schema, root_schema, &mut if_result, path, locator, depth + 1);
1802
1803 if if_result.is_valid() {
1804 if let Some(then_schema) = schema_obj.get("then") {
1805 self.validate_instance(instance, then_schema, root_schema, result, path, locator, depth + 1);
1806 }
1807 } else if let Some(else_schema) = schema_obj.get("else") {
1808 self.validate_instance(instance, else_schema, root_schema, result, path, locator, depth + 1);
1809 }
1810 }
1811 }
1812}
1813
1814#[cfg(test)]
1815mod tests {
1816 use super::*;
1817
1818 fn make_schema(type_name: &str) -> Value {
1819 serde_json::json!({
1820 "$id": "https://example.com/test",
1821 "name": "Test",
1822 "type": type_name
1823 })
1824 }
1825
1826 #[test]
1827 fn test_string_valid() {
1828 let validator = InstanceValidator::new();
1829 let schema = make_schema("string");
1830 let result = validator.validate(r#""hello""#, &schema);
1831 assert!(result.is_valid());
1832 }
1833
1834 #[test]
1835 fn test_string_invalid() {
1836 let validator = InstanceValidator::new();
1837 let schema = make_schema("string");
1838 let result = validator.validate("123", &schema);
1839 assert!(!result.is_valid());
1840 }
1841
1842 #[test]
1843 fn test_boolean_valid() {
1844 let validator = InstanceValidator::new();
1845 let schema = make_schema("boolean");
1846 let result = validator.validate("true", &schema);
1847 assert!(result.is_valid());
1848 }
1849
1850 #[test]
1851 fn test_int32_valid() {
1852 let validator = InstanceValidator::new();
1853 let schema = make_schema("int32");
1854 let result = validator.validate("42", &schema);
1855 assert!(result.is_valid());
1856 }
1857
1858 #[test]
1859 fn test_object_valid() {
1860 let validator = InstanceValidator::new();
1861 let schema = serde_json::json!({
1862 "$id": "https://example.com/test",
1863 "name": "Test",
1864 "type": "object",
1865 "properties": {
1866 "name": { "type": "string" }
1867 },
1868 "required": ["name"]
1869 });
1870 let result = validator.validate(r#"{"name": "test"}"#, &schema);
1871 assert!(result.is_valid());
1872 }
1873
1874 #[test]
1875 fn test_object_missing_required() {
1876 let validator = InstanceValidator::new();
1877 let schema = serde_json::json!({
1878 "$id": "https://example.com/test",
1879 "name": "Test",
1880 "type": "object",
1881 "properties": {
1882 "name": { "type": "string" }
1883 },
1884 "required": ["name"]
1885 });
1886 let result = validator.validate(r#"{}"#, &schema);
1887 assert!(!result.is_valid());
1888 }
1889
1890 #[test]
1891 fn test_array_valid() {
1892 let validator = InstanceValidator::new();
1893 let schema = serde_json::json!({
1894 "$id": "https://example.com/test",
1895 "name": "Test",
1896 "type": "array",
1897 "items": { "type": "int32" }
1898 });
1899 let result = validator.validate("[1, 2, 3]", &schema);
1900 assert!(result.is_valid());
1901 }
1902
1903 #[test]
1904 fn test_enum_valid() {
1905 let validator = InstanceValidator::new();
1906 let schema = serde_json::json!({
1907 "$id": "https://example.com/test",
1908 "name": "Test",
1909 "type": "string",
1910 "enum": ["a", "b", "c"]
1911 });
1912 let result = validator.validate(r#""b""#, &schema);
1913 assert!(result.is_valid());
1914 }
1915
1916 #[test]
1917 fn test_enum_invalid() {
1918 let validator = InstanceValidator::new();
1919 let schema = serde_json::json!({
1920 "$id": "https://example.com/test",
1921 "name": "Test",
1922 "type": "string",
1923 "enum": ["a", "b", "c"]
1924 });
1925 let result = validator.validate(r#""d""#, &schema);
1926 assert!(!result.is_valid());
1927 }
1928}