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" => {
305 self.validate_number(instance, schema_obj, result, path, locator)
306 }
307 "decimal" => self.validate_decimal(instance, schema_obj, result, path, locator),
308 "date" => self.validate_date(instance, result, path, locator),
309 "time" => self.validate_time(instance, result, path, locator),
310 "datetime" => self.validate_datetime(instance, result, path, locator),
311 "duration" => self.validate_duration(instance, result, path, locator),
312 "uuid" => self.validate_uuid(instance, result, path, locator),
313 "uri" => self.validate_uri(instance, result, path, locator),
314 "binary" => self.validate_binary(instance, result, path, locator),
315 "jsonpointer" => self.validate_jsonpointer(instance, result, path, locator),
316 "object" => self.validate_object(instance, schema_obj, root_schema, result, path, locator, depth),
317 "array" => self.validate_array(instance, schema_obj, root_schema, result, path, locator, depth),
318 "set" => self.validate_set(instance, schema_obj, root_schema, result, path, locator, depth),
319 "map" => self.validate_map(instance, schema_obj, root_schema, result, path, locator, depth),
320 "tuple" => self.validate_tuple(instance, schema_obj, root_schema, result, path, locator, depth),
321 "choice" => self.validate_choice(instance, schema_obj, root_schema, result, path, locator, depth),
322 "any" => {} _ => {
324 result.add_error(ValidationError::instance_error(
325 InstanceErrorCode::InstanceTypeUnknown,
326 format!("Unknown type: {}", type_name),
327 path,
328 locator.get_location(path),
329 ));
330 }
331 }
332 }
333
334 fn validate_string(
337 &self,
338 instance: &Value,
339 schema_obj: &serde_json::Map<String, Value>,
340 result: &mut ValidationResult,
341 path: &str,
342 locator: &JsonSourceLocator,
343 ) {
344 let s = match instance {
345 Value::String(s) => s,
346 _ => {
347 result.add_error(ValidationError::instance_error(
348 InstanceErrorCode::InstanceStringExpected,
349 "Expected string",
350 path,
351 locator.get_location(path),
352 ));
353 return;
354 }
355 };
356
357 if self.options.extended {
358 if let Some(Value::Number(n)) = schema_obj.get("minLength") {
360 if let Some(min) = n.as_u64() {
361 if s.chars().count() < min as usize {
362 result.add_error(ValidationError::instance_error(
363 InstanceErrorCode::InstanceStringTooShort,
364 format!("String length {} is less than minimum {}", s.chars().count(), min),
365 path,
366 locator.get_location(path),
367 ));
368 }
369 }
370 }
371
372 if let Some(Value::Number(n)) = schema_obj.get("maxLength") {
374 if let Some(max) = n.as_u64() {
375 if s.chars().count() > max as usize {
376 result.add_error(ValidationError::instance_error(
377 InstanceErrorCode::InstanceStringTooLong,
378 format!("String length {} is greater than maximum {}", s.chars().count(), max),
379 path,
380 locator.get_location(path),
381 ));
382 }
383 }
384 }
385
386 if let Some(Value::String(pattern)) = schema_obj.get("pattern") {
388 if let Ok(re) = Regex::new(pattern) {
389 if !re.is_match(s) {
390 result.add_error(ValidationError::instance_error(
391 InstanceErrorCode::InstanceStringPatternMismatch,
392 format!("String does not match pattern: {}", pattern),
393 path,
394 locator.get_location(path),
395 ));
396 }
397 }
398 }
399 }
400 }
401
402 fn validate_boolean(
403 &self,
404 instance: &Value,
405 result: &mut ValidationResult,
406 path: &str,
407 locator: &JsonSourceLocator,
408 ) {
409 if !instance.is_boolean() {
410 result.add_error(ValidationError::instance_error(
411 InstanceErrorCode::InstanceBooleanExpected,
412 "Expected boolean",
413 path,
414 locator.get_location(path),
415 ));
416 }
417 }
418
419 fn validate_null(
420 &self,
421 instance: &Value,
422 result: &mut ValidationResult,
423 path: &str,
424 locator: &JsonSourceLocator,
425 ) {
426 if !instance.is_null() {
427 result.add_error(ValidationError::instance_error(
428 InstanceErrorCode::InstanceNullExpected,
429 "Expected null",
430 path,
431 locator.get_location(path),
432 ));
433 }
434 }
435
436 fn validate_number(
437 &self,
438 instance: &Value,
439 schema_obj: &serde_json::Map<String, Value>,
440 result: &mut ValidationResult,
441 path: &str,
442 locator: &JsonSourceLocator,
443 ) {
444 let num = match instance {
445 Value::Number(n) => n,
446 _ => {
447 result.add_error(ValidationError::instance_error(
448 InstanceErrorCode::InstanceNumberExpected,
449 "Expected number",
450 path,
451 locator.get_location(path),
452 ));
453 return;
454 }
455 };
456
457 if self.options.extended {
458 let value = num.as_f64().unwrap_or(0.0);
459
460 if let Some(Value::Number(n)) = schema_obj.get("minimum") {
462 if let Some(min) = n.as_f64() {
463 if value < min {
464 result.add_error(ValidationError::instance_error(
465 InstanceErrorCode::InstanceNumberTooSmall,
466 format!("Value {} is less than minimum {}", value, min),
467 path,
468 locator.get_location(path),
469 ));
470 }
471 }
472 }
473
474 if let Some(Value::Number(n)) = schema_obj.get("maximum") {
476 if let Some(max) = n.as_f64() {
477 if value > max {
478 result.add_error(ValidationError::instance_error(
479 InstanceErrorCode::InstanceNumberTooLarge,
480 format!("Value {} is greater than maximum {}", value, max),
481 path,
482 locator.get_location(path),
483 ));
484 }
485 }
486 }
487
488 if let Some(Value::Number(n)) = schema_obj.get("exclusiveMinimum") {
490 if let Some(min) = n.as_f64() {
491 if value <= min {
492 result.add_error(ValidationError::instance_error(
493 InstanceErrorCode::InstanceNumberTooSmall,
494 format!("Value {} is not greater than exclusive minimum {}", value, min),
495 path,
496 locator.get_location(path),
497 ));
498 }
499 }
500 }
501
502 if let Some(Value::Number(n)) = schema_obj.get("exclusiveMaximum") {
504 if let Some(max) = n.as_f64() {
505 if value >= max {
506 result.add_error(ValidationError::instance_error(
507 InstanceErrorCode::InstanceNumberTooLarge,
508 format!("Value {} is not less than exclusive maximum {}", value, max),
509 path,
510 locator.get_location(path),
511 ));
512 }
513 }
514 }
515 }
516 }
517
518 fn validate_decimal(
522 &self,
523 instance: &Value,
524 schema_obj: &serde_json::Map<String, Value>,
525 result: &mut ValidationResult,
526 path: &str,
527 locator: &JsonSourceLocator,
528 ) {
529 let value: f64 = match instance {
530 Value::String(s) => {
531 match s.parse::<f64>() {
533 Ok(v) => v,
534 Err(_) => {
535 result.add_error(ValidationError::instance_error(
536 InstanceErrorCode::InstanceDecimalExpected,
537 format!("Invalid decimal format: {}", s),
538 path,
539 locator.get_location(path),
540 ));
541 return;
542 }
543 }
544 }
545 Value::Number(n) => {
546 n.as_f64().unwrap_or(0.0)
548 }
549 _ => {
550 result.add_error(ValidationError::instance_error(
551 InstanceErrorCode::InstanceDecimalExpected,
552 "Expected decimal (as string or number)",
553 path,
554 locator.get_location(path),
555 ));
556 return;
557 }
558 };
559
560 if self.options.extended {
562 if let Some(min_val) = schema_obj.get("minimum") {
564 let min = match min_val {
565 Value::Number(n) => n.as_f64(),
566 Value::String(s) => s.parse::<f64>().ok(),
567 _ => None,
568 };
569 if let Some(min) = min {
570 if value < min {
571 result.add_error(ValidationError::instance_error(
572 InstanceErrorCode::InstanceNumberTooSmall,
573 format!("Value {} is less than minimum {}", value, min),
574 path,
575 locator.get_location(path),
576 ));
577 }
578 }
579 }
580
581 if let Some(max_val) = schema_obj.get("maximum") {
583 let max = match max_val {
584 Value::Number(n) => n.as_f64(),
585 Value::String(s) => s.parse::<f64>().ok(),
586 _ => None,
587 };
588 if let Some(max) = max {
589 if value > max {
590 result.add_error(ValidationError::instance_error(
591 InstanceErrorCode::InstanceNumberTooLarge,
592 format!("Value {} is greater than maximum {}", value, max),
593 path,
594 locator.get_location(path),
595 ));
596 }
597 }
598 }
599
600 if let Some(min_val) = schema_obj.get("exclusiveMinimum") {
602 let min = match min_val {
603 Value::Number(n) => n.as_f64(),
604 Value::String(s) => s.parse::<f64>().ok(),
605 _ => None,
606 };
607 if let Some(min) = min {
608 if value <= min {
609 result.add_error(ValidationError::instance_error(
610 InstanceErrorCode::InstanceNumberTooSmall,
611 format!("Value {} is not greater than exclusive minimum {}", value, min),
612 path,
613 locator.get_location(path),
614 ));
615 }
616 }
617 }
618
619 if let Some(max_val) = schema_obj.get("exclusiveMaximum") {
621 let max = match max_val {
622 Value::Number(n) => n.as_f64(),
623 Value::String(s) => s.parse::<f64>().ok(),
624 _ => None,
625 };
626 if let Some(max) = max {
627 if value >= max {
628 result.add_error(ValidationError::instance_error(
629 InstanceErrorCode::InstanceNumberTooLarge,
630 format!("Value {} is not less than exclusive maximum {}", value, max),
631 path,
632 locator.get_location(path),
633 ));
634 }
635 }
636 }
637 }
638 }
639
640 fn validate_int32(
641 &self,
642 instance: &Value,
643 _schema_obj: &serde_json::Map<String, Value>,
644 result: &mut ValidationResult,
645 path: &str,
646 locator: &JsonSourceLocator,
647 ) {
648 self.validate_int_range(instance, _schema_obj, result, path, locator, i32::MIN as i64, i32::MAX as i64, "int32")
649 }
650
651 fn validate_int_range(
652 &self,
653 instance: &Value,
654 schema_obj: &serde_json::Map<String, Value>,
655 result: &mut ValidationResult,
656 path: &str,
657 locator: &JsonSourceLocator,
658 min: i64,
659 max: i64,
660 type_name: &str,
661 ) {
662 let num = match instance {
663 Value::Number(n) => n,
664 _ => {
665 result.add_error(ValidationError::instance_error(
666 InstanceErrorCode::InstanceIntegerExpected,
667 format!("Expected {}", type_name),
668 path,
669 locator.get_location(path),
670 ));
671 return;
672 }
673 };
674
675 let val = if let Some(v) = num.as_i64() {
676 if v < min || v > max {
677 result.add_error(ValidationError::instance_error(
678 InstanceErrorCode::InstanceIntegerOutOfRange,
679 format!("Value {} is out of range for {} ({} to {})", v, type_name, min, max),
680 path,
681 locator.get_location(path),
682 ));
683 }
684 v as f64
685 } else if let Some(v) = num.as_f64() {
686 if v.fract() != 0.0 {
687 result.add_error(ValidationError::instance_error(
688 InstanceErrorCode::InstanceIntegerExpected,
689 format!("Expected integer, got {}", v),
690 path,
691 locator.get_location(path),
692 ));
693 return;
694 }
695 if v < min as f64 || v > max as f64 {
696 result.add_error(ValidationError::instance_error(
697 InstanceErrorCode::InstanceIntegerOutOfRange,
698 format!("Value {} is out of range for {}", v, type_name),
699 path,
700 locator.get_location(path),
701 ));
702 }
703 v
704 } else {
705 return;
706 };
707
708 if self.options.extended {
710 if let Some(Value::Number(n)) = schema_obj.get("minimum") {
712 if let Some(min_val) = n.as_f64() {
713 if val < min_val {
714 result.add_error(ValidationError::instance_error(
715 InstanceErrorCode::InstanceNumberTooSmall,
716 format!("Value {} is less than minimum {}", val, min_val),
717 path,
718 locator.get_location(path),
719 ));
720 }
721 }
722 }
723
724 if let Some(Value::Number(n)) = schema_obj.get("maximum") {
726 if let Some(max_val) = n.as_f64() {
727 if val > max_val {
728 result.add_error(ValidationError::instance_error(
729 InstanceErrorCode::InstanceNumberTooLarge,
730 format!("Value {} is greater than maximum {}", val, max_val),
731 path,
732 locator.get_location(path),
733 ));
734 }
735 }
736 }
737
738 if let Some(Value::Number(n)) = schema_obj.get("exclusiveMinimum") {
740 if let Some(min_val) = n.as_f64() {
741 if val <= min_val {
742 result.add_error(ValidationError::instance_error(
743 InstanceErrorCode::InstanceNumberTooSmall,
744 format!("Value {} is not greater than exclusive minimum {}", val, min_val),
745 path,
746 locator.get_location(path),
747 ));
748 }
749 }
750 }
751
752 if let Some(Value::Number(n)) = schema_obj.get("exclusiveMaximum") {
754 if let Some(max_val) = n.as_f64() {
755 if val >= max_val {
756 result.add_error(ValidationError::instance_error(
757 InstanceErrorCode::InstanceNumberTooLarge,
758 format!("Value {} is not less than exclusive maximum {}", val, max_val),
759 path,
760 locator.get_location(path),
761 ));
762 }
763 }
764 }
765
766 if let Some(Value::Number(n)) = schema_obj.get("multipleOf") {
768 if let Some(mul) = n.as_f64() {
769 if mul > 0.0 && (val % mul).abs() > f64::EPSILON {
770 result.add_error(ValidationError::instance_error(
771 InstanceErrorCode::InstanceNumberNotMultiple,
772 format!("Value {} is not a multiple of {}", val, mul),
773 path,
774 locator.get_location(path),
775 ));
776 }
777 }
778 }
779 }
780 }
781
782 fn validate_uint_range(
783 &self,
784 instance: &Value,
785 schema_obj: &serde_json::Map<String, Value>,
786 result: &mut ValidationResult,
787 path: &str,
788 locator: &JsonSourceLocator,
789 min: u64,
790 max: u64,
791 type_name: &str,
792 ) {
793 let num = match instance {
794 Value::Number(n) => n,
795 _ => {
796 result.add_error(ValidationError::instance_error(
797 InstanceErrorCode::InstanceIntegerExpected,
798 format!("Expected {}", type_name),
799 path,
800 locator.get_location(path),
801 ));
802 return;
803 }
804 };
805
806 if let Some(val) = num.as_u64() {
807 if val < min || val > max {
808 result.add_error(ValidationError::instance_error(
809 InstanceErrorCode::InstanceIntegerOutOfRange,
810 format!("Value {} is out of range for {} ({} to {})", val, type_name, min, max),
811 path,
812 locator.get_location(path),
813 ));
814 }
815 } else if let Some(v) = num.as_i64() {
816 if v < 0 {
817 result.add_error(ValidationError::instance_error(
818 InstanceErrorCode::InstanceIntegerOutOfRange,
819 format!("Value {} is negative, expected unsigned {}", v, type_name),
820 path,
821 locator.get_location(path),
822 ));
823 return;
824 }
825 } else {
826 return;
827 }
828
829 let val = num.as_f64().unwrap_or(0.0);
831
832 if self.options.extended {
834 if let Some(Value::Number(n)) = schema_obj.get("minimum") {
836 if let Some(min_val) = n.as_f64() {
837 if val < min_val {
838 result.add_error(ValidationError::instance_error(
839 InstanceErrorCode::InstanceNumberTooSmall,
840 format!("Value {} is less than minimum {}", val, min_val),
841 path,
842 locator.get_location(path),
843 ));
844 }
845 }
846 }
847
848 if let Some(Value::Number(n)) = schema_obj.get("maximum") {
850 if let Some(max_val) = n.as_f64() {
851 if val > max_val {
852 result.add_error(ValidationError::instance_error(
853 InstanceErrorCode::InstanceNumberTooLarge,
854 format!("Value {} is greater than maximum {}", val, max_val),
855 path,
856 locator.get_location(path),
857 ));
858 }
859 }
860 }
861
862 if let Some(Value::Number(n)) = schema_obj.get("exclusiveMinimum") {
864 if let Some(min_val) = n.as_f64() {
865 if val <= min_val {
866 result.add_error(ValidationError::instance_error(
867 InstanceErrorCode::InstanceNumberTooSmall,
868 format!("Value {} is not greater than exclusive minimum {}", val, min_val),
869 path,
870 locator.get_location(path),
871 ));
872 }
873 }
874 }
875
876 if let Some(Value::Number(n)) = schema_obj.get("exclusiveMaximum") {
878 if let Some(max_val) = n.as_f64() {
879 if val >= max_val {
880 result.add_error(ValidationError::instance_error(
881 InstanceErrorCode::InstanceNumberTooLarge,
882 format!("Value {} is not less than exclusive maximum {}", val, max_val),
883 path,
884 locator.get_location(path),
885 ));
886 }
887 }
888 }
889
890 if let Some(Value::Number(n)) = schema_obj.get("multipleOf") {
892 if let Some(mul) = n.as_f64() {
893 if mul > 0.0 && (val % mul).abs() > f64::EPSILON {
894 result.add_error(ValidationError::instance_error(
895 InstanceErrorCode::InstanceNumberNotMultiple,
896 format!("Value {} is not a multiple of {}", val, mul),
897 path,
898 locator.get_location(path),
899 ));
900 }
901 }
902 }
903 }
904 }
905
906 fn validate_int64(
907 &self,
908 instance: &Value,
909 _schema_obj: &serde_json::Map<String, Value>,
910 result: &mut ValidationResult,
911 path: &str,
912 locator: &JsonSourceLocator,
913 ) {
914 match instance {
915 Value::Number(n) => {
916 if n.as_i64().is_none() && n.as_f64().is_none_or(|f| f.fract() != 0.0) {
917 result.add_error(ValidationError::instance_error(
918 InstanceErrorCode::InstanceIntegerExpected,
919 "Expected int64",
920 path,
921 locator.get_location(path),
922 ));
923 }
924 }
925 Value::String(s) => {
926 if s.parse::<i64>().is_err() {
928 result.add_error(ValidationError::instance_error(
929 InstanceErrorCode::InstanceIntegerExpected,
930 "Expected int64 (as number or string)",
931 path,
932 locator.get_location(path),
933 ));
934 }
935 }
936 _ => {
937 result.add_error(ValidationError::instance_error(
938 InstanceErrorCode::InstanceIntegerExpected,
939 "Expected int64",
940 path,
941 locator.get_location(path),
942 ));
943 }
944 }
945 }
946
947 fn validate_int128(
948 &self,
949 instance: &Value,
950 _schema_obj: &serde_json::Map<String, Value>,
951 result: &mut ValidationResult,
952 path: &str,
953 locator: &JsonSourceLocator,
954 ) {
955 match instance {
956 Value::Number(_) => {} Value::String(s) => {
958 if s.parse::<i128>().is_err() {
959 result.add_error(ValidationError::instance_error(
960 InstanceErrorCode::InstanceIntegerExpected,
961 "Expected int128 (as number or string)",
962 path,
963 locator.get_location(path),
964 ));
965 }
966 }
967 _ => {
968 result.add_error(ValidationError::instance_error(
969 InstanceErrorCode::InstanceIntegerExpected,
970 "Expected int128",
971 path,
972 locator.get_location(path),
973 ));
974 }
975 }
976 }
977
978 fn validate_uint32(
979 &self,
980 instance: &Value,
981 _schema_obj: &serde_json::Map<String, Value>,
982 result: &mut ValidationResult,
983 path: &str,
984 locator: &JsonSourceLocator,
985 ) {
986 self.validate_uint_range(instance, _schema_obj, result, path, locator, 0, u32::MAX as u64, "uint32")
987 }
988
989 fn validate_uint64(
990 &self,
991 instance: &Value,
992 _schema_obj: &serde_json::Map<String, Value>,
993 result: &mut ValidationResult,
994 path: &str,
995 locator: &JsonSourceLocator,
996 ) {
997 match instance {
998 Value::Number(n) => {
999 if n.as_u64().is_none() {
1000 if let Some(i) = n.as_i64() {
1001 if i < 0 {
1002 result.add_error(ValidationError::instance_error(
1003 InstanceErrorCode::InstanceIntegerOutOfRange,
1004 "Expected unsigned uint64",
1005 path,
1006 locator.get_location(path),
1007 ));
1008 }
1009 }
1010 }
1011 }
1012 Value::String(s) => {
1013 if s.parse::<u64>().is_err() {
1014 result.add_error(ValidationError::instance_error(
1015 InstanceErrorCode::InstanceIntegerExpected,
1016 "Expected uint64 (as number or string)",
1017 path,
1018 locator.get_location(path),
1019 ));
1020 }
1021 }
1022 _ => {
1023 result.add_error(ValidationError::instance_error(
1024 InstanceErrorCode::InstanceIntegerExpected,
1025 "Expected uint64",
1026 path,
1027 locator.get_location(path),
1028 ));
1029 }
1030 }
1031 }
1032
1033 fn validate_uint128(
1034 &self,
1035 instance: &Value,
1036 _schema_obj: &serde_json::Map<String, Value>,
1037 result: &mut ValidationResult,
1038 path: &str,
1039 locator: &JsonSourceLocator,
1040 ) {
1041 match instance {
1042 Value::Number(n) => {
1043 if let Some(i) = n.as_i64() {
1044 if i < 0 {
1045 result.add_error(ValidationError::instance_error(
1046 InstanceErrorCode::InstanceIntegerOutOfRange,
1047 "Expected unsigned uint128",
1048 path,
1049 locator.get_location(path),
1050 ));
1051 }
1052 }
1053 }
1054 Value::String(s) => {
1055 if s.parse::<u128>().is_err() {
1056 result.add_error(ValidationError::instance_error(
1057 InstanceErrorCode::InstanceIntegerExpected,
1058 "Expected uint128 (as number or string)",
1059 path,
1060 locator.get_location(path),
1061 ));
1062 }
1063 }
1064 _ => {
1065 result.add_error(ValidationError::instance_error(
1066 InstanceErrorCode::InstanceIntegerExpected,
1067 "Expected uint128",
1068 path,
1069 locator.get_location(path),
1070 ));
1071 }
1072 }
1073 }
1074
1075 fn validate_date(
1078 &self,
1079 instance: &Value,
1080 result: &mut ValidationResult,
1081 path: &str,
1082 locator: &JsonSourceLocator,
1083 ) {
1084 let s = match instance {
1085 Value::String(s) => s,
1086 _ => {
1087 result.add_error(ValidationError::instance_error(
1088 InstanceErrorCode::InstanceDateExpected,
1089 "Expected date string",
1090 path,
1091 locator.get_location(path),
1092 ));
1093 return;
1094 }
1095 };
1096
1097 if NaiveDate::parse_from_str(s, "%Y-%m-%d").is_err() {
1098 result.add_error(ValidationError::instance_error(
1099 InstanceErrorCode::InstanceDateInvalid,
1100 format!("Invalid date format: {}", s),
1101 path,
1102 locator.get_location(path),
1103 ));
1104 }
1105 }
1106
1107 fn validate_time(
1108 &self,
1109 instance: &Value,
1110 result: &mut ValidationResult,
1111 path: &str,
1112 locator: &JsonSourceLocator,
1113 ) {
1114 let s = match instance {
1115 Value::String(s) => s,
1116 _ => {
1117 result.add_error(ValidationError::instance_error(
1118 InstanceErrorCode::InstanceTimeExpected,
1119 "Expected time string",
1120 path,
1121 locator.get_location(path),
1122 ));
1123 return;
1124 }
1125 };
1126
1127 let valid = NaiveTime::parse_from_str(s, "%H:%M:%S").is_ok()
1129 || NaiveTime::parse_from_str(s, "%H:%M:%S%.f").is_ok()
1130 || NaiveTime::parse_from_str(s, "%H:%M").is_ok();
1131
1132 if !valid {
1133 result.add_error(ValidationError::instance_error(
1134 InstanceErrorCode::InstanceTimeInvalid,
1135 format!("Invalid time format: {}", s),
1136 path,
1137 locator.get_location(path),
1138 ));
1139 }
1140 }
1141
1142 fn validate_datetime(
1143 &self,
1144 instance: &Value,
1145 result: &mut ValidationResult,
1146 path: &str,
1147 locator: &JsonSourceLocator,
1148 ) {
1149 let s = match instance {
1150 Value::String(s) => s,
1151 _ => {
1152 result.add_error(ValidationError::instance_error(
1153 InstanceErrorCode::InstanceDateTimeExpected,
1154 "Expected datetime string",
1155 path,
1156 locator.get_location(path),
1157 ));
1158 return;
1159 }
1160 };
1161
1162 if DateTime::parse_from_rfc3339(s).is_err() {
1164 result.add_error(ValidationError::instance_error(
1165 InstanceErrorCode::InstanceDateTimeInvalid,
1166 format!("Invalid datetime format (expected RFC 3339): {}", s),
1167 path,
1168 locator.get_location(path),
1169 ));
1170 }
1171 }
1172
1173 fn validate_duration(
1174 &self,
1175 instance: &Value,
1176 result: &mut ValidationResult,
1177 path: &str,
1178 locator: &JsonSourceLocator,
1179 ) {
1180 let s = match instance {
1181 Value::String(s) => s,
1182 _ => {
1183 result.add_error(ValidationError::instance_error(
1184 InstanceErrorCode::InstanceDurationExpected,
1185 "Expected duration string",
1186 path,
1187 locator.get_location(path),
1188 ));
1189 return;
1190 }
1191 };
1192
1193 let duration_pattern = Regex::new(r"^P(\d+Y)?(\d+M)?(\d+W)?(\d+D)?(T(\d+H)?(\d+M)?(\d+(\.\d+)?S)?)?$").unwrap();
1195 if !duration_pattern.is_match(s) {
1196 result.add_error(ValidationError::instance_error(
1197 InstanceErrorCode::InstanceDurationInvalid,
1198 format!("Invalid duration format (expected ISO 8601): {}", s),
1199 path,
1200 locator.get_location(path),
1201 ));
1202 }
1203 }
1204
1205 fn validate_uuid(
1208 &self,
1209 instance: &Value,
1210 result: &mut ValidationResult,
1211 path: &str,
1212 locator: &JsonSourceLocator,
1213 ) {
1214 let s = match instance {
1215 Value::String(s) => s,
1216 _ => {
1217 result.add_error(ValidationError::instance_error(
1218 InstanceErrorCode::InstanceUuidExpected,
1219 "Expected UUID string",
1220 path,
1221 locator.get_location(path),
1222 ));
1223 return;
1224 }
1225 };
1226
1227 if Uuid::parse_str(s).is_err() {
1228 result.add_error(ValidationError::instance_error(
1229 InstanceErrorCode::InstanceUuidInvalid,
1230 format!("Invalid UUID format: {}", s),
1231 path,
1232 locator.get_location(path),
1233 ));
1234 }
1235 }
1236
1237 fn validate_uri(
1238 &self,
1239 instance: &Value,
1240 result: &mut ValidationResult,
1241 path: &str,
1242 locator: &JsonSourceLocator,
1243 ) {
1244 let s = match instance {
1245 Value::String(s) => s,
1246 _ => {
1247 result.add_error(ValidationError::instance_error(
1248 InstanceErrorCode::InstanceUriExpected,
1249 "Expected URI string",
1250 path,
1251 locator.get_location(path),
1252 ));
1253 return;
1254 }
1255 };
1256
1257 if url::Url::parse(s).is_err() {
1258 result.add_error(ValidationError::instance_error(
1259 InstanceErrorCode::InstanceUriInvalid,
1260 format!("Invalid URI format: {}", s),
1261 path,
1262 locator.get_location(path),
1263 ));
1264 }
1265 }
1266
1267 fn validate_binary(
1268 &self,
1269 instance: &Value,
1270 result: &mut ValidationResult,
1271 path: &str,
1272 locator: &JsonSourceLocator,
1273 ) {
1274 let s = match instance {
1275 Value::String(s) => s,
1276 _ => {
1277 result.add_error(ValidationError::instance_error(
1278 InstanceErrorCode::InstanceBinaryExpected,
1279 "Expected base64 string",
1280 path,
1281 locator.get_location(path),
1282 ));
1283 return;
1284 }
1285 };
1286
1287 if base64::engine::general_purpose::STANDARD.decode(s).is_err() {
1288 result.add_error(ValidationError::instance_error(
1289 InstanceErrorCode::InstanceBinaryInvalid,
1290 format!("Invalid base64 encoding: {}", s),
1291 path,
1292 locator.get_location(path),
1293 ));
1294 }
1295 }
1296
1297 fn validate_jsonpointer(
1298 &self,
1299 instance: &Value,
1300 result: &mut ValidationResult,
1301 path: &str,
1302 locator: &JsonSourceLocator,
1303 ) {
1304 let s = match instance {
1305 Value::String(s) => s,
1306 _ => {
1307 result.add_error(ValidationError::instance_error(
1308 InstanceErrorCode::InstanceJsonPointerExpected,
1309 "Expected JSON Pointer string",
1310 path,
1311 locator.get_location(path),
1312 ));
1313 return;
1314 }
1315 };
1316
1317 if !s.is_empty() && !s.starts_with('/') {
1319 result.add_error(ValidationError::instance_error(
1320 InstanceErrorCode::InstanceJsonPointerInvalid,
1321 format!("Invalid JSON Pointer format: {}", s),
1322 path,
1323 locator.get_location(path),
1324 ));
1325 }
1326 }
1327
1328 fn validate_object(
1331 &self,
1332 instance: &Value,
1333 schema_obj: &serde_json::Map<String, Value>,
1334 root_schema: &Value,
1335 result: &mut ValidationResult,
1336 path: &str,
1337 locator: &JsonSourceLocator,
1338 depth: usize,
1339 ) {
1340 let obj = match instance {
1341 Value::Object(o) => o,
1342 _ => {
1343 result.add_error(ValidationError::instance_error(
1344 InstanceErrorCode::InstanceObjectExpected,
1345 "Expected object",
1346 path,
1347 locator.get_location(path),
1348 ));
1349 return;
1350 }
1351 };
1352
1353 let properties = schema_obj.get("properties").and_then(Value::as_object);
1354 let required = schema_obj.get("required").and_then(Value::as_array);
1355 let additional_properties = schema_obj.get("additionalProperties");
1356
1357 if let Some(req) = required {
1359 for item in req {
1360 if let Value::String(prop_name) = item {
1361 if !obj.contains_key(prop_name) {
1362 result.add_error(ValidationError::instance_error(
1363 InstanceErrorCode::InstanceRequiredMissing,
1364 format!("Required property missing: {}", prop_name),
1365 path,
1366 locator.get_location(path),
1367 ));
1368 }
1369 }
1370 }
1371 }
1372
1373 for (prop_name, prop_value) in obj {
1375 let prop_path = format!("{}/{}", path, prop_name);
1376
1377 if let Some(props) = properties {
1378 if let Some(prop_schema) = props.get(prop_name) {
1379 self.validate_instance(prop_value, prop_schema, root_schema, result, &prop_path, locator, depth + 1);
1380 } else {
1381 match additional_properties {
1383 Some(Value::Bool(false)) => {
1384 result.add_error(ValidationError::instance_error(
1385 InstanceErrorCode::InstanceAdditionalProperty,
1386 format!("Additional property not allowed: {}", prop_name),
1387 &prop_path,
1388 locator.get_location(&prop_path),
1389 ));
1390 }
1391 Some(Value::Object(_)) => {
1392 self.validate_instance(prop_value, additional_properties.unwrap(), root_schema, result, &prop_path, locator, depth + 1);
1393 }
1394 _ => {}
1395 }
1396 }
1397 }
1398 }
1399
1400 if self.options.extended {
1402 if let Some(Value::Number(n)) = schema_obj.get("minProperties") {
1404 if let Some(min) = n.as_u64() {
1405 if obj.len() < min as usize {
1406 result.add_error(ValidationError::instance_error(
1407 InstanceErrorCode::InstanceTooFewProperties,
1408 format!("Object has {} properties, minimum is {}", obj.len(), min),
1409 path,
1410 locator.get_location(path),
1411 ));
1412 }
1413 }
1414 }
1415
1416 if let Some(Value::Number(n)) = schema_obj.get("maxProperties") {
1418 if let Some(max) = n.as_u64() {
1419 if obj.len() > max as usize {
1420 result.add_error(ValidationError::instance_error(
1421 InstanceErrorCode::InstanceTooManyProperties,
1422 format!("Object has {} properties, maximum is {}", obj.len(), max),
1423 path,
1424 locator.get_location(path),
1425 ));
1426 }
1427 }
1428 }
1429
1430 if let Some(Value::Object(deps)) = schema_obj.get("dependentRequired") {
1432 for (prop, required_props) in deps {
1433 if obj.contains_key(prop) {
1434 if let Value::Array(req) = required_props {
1435 for req_prop in req {
1436 if let Value::String(req_name) = req_prop {
1437 if !obj.contains_key(req_name) {
1438 result.add_error(ValidationError::instance_error(
1439 InstanceErrorCode::InstanceDependentRequiredMissing,
1440 format!("Property '{}' requires '{}' to be present", prop, req_name),
1441 path,
1442 locator.get_location(path),
1443 ));
1444 }
1445 }
1446 }
1447 }
1448 }
1449 }
1450 }
1451 }
1452 }
1453
1454 fn validate_array(
1455 &self,
1456 instance: &Value,
1457 schema_obj: &serde_json::Map<String, Value>,
1458 root_schema: &Value,
1459 result: &mut ValidationResult,
1460 path: &str,
1461 locator: &JsonSourceLocator,
1462 depth: usize,
1463 ) {
1464 let arr = match instance {
1465 Value::Array(a) => a,
1466 _ => {
1467 result.add_error(ValidationError::instance_error(
1468 InstanceErrorCode::InstanceArrayExpected,
1469 "Expected array",
1470 path,
1471 locator.get_location(path),
1472 ));
1473 return;
1474 }
1475 };
1476
1477 if let Some(items_schema) = schema_obj.get("items") {
1479 for (i, item) in arr.iter().enumerate() {
1480 let item_path = format!("{}/{}", path, i);
1481 self.validate_instance(item, items_schema, root_schema, result, &item_path, locator, depth + 1);
1482 }
1483 }
1484
1485 if self.options.extended {
1486 if let Some(Value::Number(n)) = schema_obj.get("minItems") {
1488 if let Some(min) = n.as_u64() {
1489 if arr.len() < min as usize {
1490 result.add_error(ValidationError::instance_error(
1491 InstanceErrorCode::InstanceArrayTooShort,
1492 format!("Array length {} is less than minimum {}", arr.len(), min),
1493 path,
1494 locator.get_location(path),
1495 ));
1496 }
1497 }
1498 }
1499
1500 if let Some(Value::Number(n)) = schema_obj.get("maxItems") {
1502 if let Some(max) = n.as_u64() {
1503 if arr.len() > max as usize {
1504 result.add_error(ValidationError::instance_error(
1505 InstanceErrorCode::InstanceArrayTooLong,
1506 format!("Array length {} is greater than maximum {}", arr.len(), max),
1507 path,
1508 locator.get_location(path),
1509 ));
1510 }
1511 }
1512 }
1513
1514 if let Some(contains_schema) = schema_obj.get("contains") {
1516 let mut match_count = 0;
1517 for item in arr.iter() {
1518 let mut temp_result = ValidationResult::new();
1519 self.validate_instance(item, contains_schema, root_schema, &mut temp_result, path, locator, depth + 1);
1520 if temp_result.is_valid() {
1521 match_count += 1;
1522 }
1523 }
1524
1525 let min_contains = schema_obj.get("minContains")
1526 .and_then(Value::as_u64)
1527 .unwrap_or(1);
1528 let max_contains = schema_obj.get("maxContains")
1529 .and_then(Value::as_u64);
1530
1531 if match_count < min_contains as usize {
1532 result.add_error(ValidationError::instance_error(
1533 InstanceErrorCode::InstanceArrayContainsTooFew,
1534 format!("Array contains {} matching items, minimum is {}", match_count, min_contains),
1535 path,
1536 locator.get_location(path),
1537 ));
1538 }
1539
1540 if let Some(max) = max_contains {
1541 if match_count > max as usize {
1542 result.add_error(ValidationError::instance_error(
1543 InstanceErrorCode::InstanceArrayContainsTooMany,
1544 format!("Array contains {} matching items, maximum is {}", match_count, max),
1545 path,
1546 locator.get_location(path),
1547 ));
1548 }
1549 }
1550 }
1551 }
1552 }
1553
1554 fn validate_set(
1555 &self,
1556 instance: &Value,
1557 schema_obj: &serde_json::Map<String, Value>,
1558 root_schema: &Value,
1559 result: &mut ValidationResult,
1560 path: &str,
1561 locator: &JsonSourceLocator,
1562 depth: usize,
1563 ) {
1564 let arr = match instance {
1565 Value::Array(a) => a,
1566 _ => {
1567 result.add_error(ValidationError::instance_error(
1568 InstanceErrorCode::InstanceSetExpected,
1569 "Expected array (set)",
1570 path,
1571 locator.get_location(path),
1572 ));
1573 return;
1574 }
1575 };
1576
1577 let mut seen = Vec::new();
1579 for (i, item) in arr.iter().enumerate() {
1580 let item_str = item.to_string();
1581 if seen.contains(&item_str) {
1582 result.add_error(ValidationError::instance_error(
1583 InstanceErrorCode::InstanceSetNotUnique,
1584 "Set contains duplicate values",
1585 &format!("{}/{}", path, i),
1586 locator.get_location(&format!("{}/{}", path, i)),
1587 ));
1588 } else {
1589 seen.push(item_str);
1590 }
1591 }
1592
1593 if let Some(items_schema) = schema_obj.get("items") {
1595 for (i, item) in arr.iter().enumerate() {
1596 let item_path = format!("{}/{}", path, i);
1597 self.validate_instance(item, items_schema, root_schema, result, &item_path, locator, depth + 1);
1598 }
1599 }
1600 }
1601
1602 fn validate_map(
1603 &self,
1604 instance: &Value,
1605 schema_obj: &serde_json::Map<String, Value>,
1606 root_schema: &Value,
1607 result: &mut ValidationResult,
1608 path: &str,
1609 locator: &JsonSourceLocator,
1610 depth: usize,
1611 ) {
1612 let obj = match instance {
1613 Value::Object(o) => o,
1614 _ => {
1615 result.add_error(ValidationError::instance_error(
1616 InstanceErrorCode::InstanceMapExpected,
1617 "Expected object (map)",
1618 path,
1619 locator.get_location(path),
1620 ));
1621 return;
1622 }
1623 };
1624
1625 if let Some(values_schema) = schema_obj.get("values") {
1627 for (key, value) in obj.iter() {
1628 let value_path = format!("{}/{}", path, key);
1629 self.validate_instance(value, values_schema, root_schema, result, &value_path, locator, depth + 1);
1630 }
1631 }
1632
1633 if self.options.extended {
1635 if let Some(Value::Number(n)) = schema_obj.get("minEntries") {
1637 if let Some(min) = n.as_u64() {
1638 if obj.len() < min as usize {
1639 result.add_error(ValidationError::instance_error(
1640 InstanceErrorCode::InstanceMapTooFewEntries,
1641 format!("Map has {} entries, minimum is {}", obj.len(), min),
1642 path,
1643 locator.get_location(path),
1644 ));
1645 }
1646 }
1647 }
1648
1649 if let Some(Value::Number(n)) = schema_obj.get("maxEntries") {
1651 if let Some(max) = n.as_u64() {
1652 if obj.len() > max as usize {
1653 result.add_error(ValidationError::instance_error(
1654 InstanceErrorCode::InstanceMapTooManyEntries,
1655 format!("Map has {} entries, maximum is {}", obj.len(), max),
1656 path,
1657 locator.get_location(path),
1658 ));
1659 }
1660 }
1661 }
1662
1663 if let Some(keynames_schema) = schema_obj.get("keyNames") {
1665 if let Some(keynames_obj) = keynames_schema.as_object() {
1666 if let Some(Value::String(pattern)) = keynames_obj.get("pattern") {
1667 if let Ok(re) = Regex::new(pattern) {
1668 for key in obj.keys() {
1669 if !re.is_match(key) {
1670 result.add_error(ValidationError::instance_error(
1671 InstanceErrorCode::InstanceMapKeyPatternMismatch,
1672 format!("Map key '{}' does not match pattern '{}'", key, pattern),
1673 path,
1674 locator.get_location(path),
1675 ));
1676 }
1677 }
1678 }
1679 }
1680 }
1681 }
1682 }
1683 }
1684
1685 fn validate_tuple(
1686 &self,
1687 instance: &Value,
1688 schema_obj: &serde_json::Map<String, Value>,
1689 root_schema: &Value,
1690 result: &mut ValidationResult,
1691 path: &str,
1692 locator: &JsonSourceLocator,
1693 depth: usize,
1694 ) {
1695 let arr = match instance {
1696 Value::Array(a) => a,
1697 _ => {
1698 result.add_error(ValidationError::instance_error(
1699 InstanceErrorCode::InstanceTupleExpected,
1700 "Expected array (tuple)",
1701 path,
1702 locator.get_location(path),
1703 ));
1704 return;
1705 }
1706 };
1707
1708 let properties = schema_obj.get("properties").and_then(Value::as_object);
1709 let tuple_order = schema_obj.get("tuple").and_then(Value::as_array);
1710
1711 if let (Some(props), Some(order)) = (properties, tuple_order) {
1712 if arr.len() != order.len() {
1714 result.add_error(ValidationError::instance_error(
1715 InstanceErrorCode::InstanceTupleLengthMismatch,
1716 format!("Tuple length {} does not match expected {}", arr.len(), order.len()),
1717 path,
1718 locator.get_location(path),
1719 ));
1720 return;
1721 }
1722
1723 for (i, prop_name_val) in order.iter().enumerate() {
1725 if let Value::String(prop_name) = prop_name_val {
1726 if let Some(prop_schema) = props.get(prop_name) {
1727 let elem_path = format!("{}/{}", path, i);
1728 self.validate_instance(&arr[i], prop_schema, root_schema, result, &elem_path, locator, depth + 1);
1729 }
1730 }
1731 }
1732 }
1733 }
1734
1735 fn validate_choice(
1736 &self,
1737 instance: &Value,
1738 schema_obj: &serde_json::Map<String, Value>,
1739 root_schema: &Value,
1740 result: &mut ValidationResult,
1741 path: &str,
1742 locator: &JsonSourceLocator,
1743 depth: usize,
1744 ) {
1745 let choices = match schema_obj.get("choices").and_then(Value::as_object) {
1746 Some(c) => c,
1747 None => return,
1748 };
1749
1750 let selector = schema_obj.get("selector").and_then(Value::as_str);
1751
1752 if let Some(selector_prop) = selector {
1753 let obj = match instance {
1755 Value::Object(o) => o,
1756 _ => {
1757 result.add_error(ValidationError::instance_error(
1758 InstanceErrorCode::InstanceObjectExpected,
1759 "Choice with selector expects object",
1760 path,
1761 locator.get_location(path),
1762 ));
1763 return;
1764 }
1765 };
1766
1767 let selector_value = match obj.get(selector_prop) {
1768 Some(Value::String(s)) => s.as_str(),
1769 Some(_) => {
1770 result.add_error(ValidationError::instance_error(
1771 InstanceErrorCode::InstanceChoiceSelectorInvalid,
1772 format!("Selector property '{}' must be a string", selector_prop),
1773 &format!("{}/{}", path, selector_prop),
1774 locator.get_location(&format!("{}/{}", path, selector_prop)),
1775 ));
1776 return;
1777 }
1778 None => {
1779 result.add_error(ValidationError::instance_error(
1780 InstanceErrorCode::InstanceChoiceSelectorMissing,
1781 format!("Missing selector property: {}", selector_prop),
1782 path,
1783 locator.get_location(path),
1784 ));
1785 return;
1786 }
1787 };
1788
1789 if let Some(choice_schema) = choices.get(selector_value) {
1790 self.validate_instance(instance, choice_schema, root_schema, result, path, locator, depth + 1);
1791 } else {
1792 result.add_error(ValidationError::instance_error(
1793 InstanceErrorCode::InstanceChoiceUnknown,
1794 format!("Unknown choice: {}", selector_value),
1795 path,
1796 locator.get_location(path),
1797 ));
1798 }
1799 } else {
1800 let obj = match instance {
1803 Value::Object(o) => o,
1804 _ => {
1805 result.add_error(ValidationError::instance_error(
1806 InstanceErrorCode::InstanceChoiceNoMatch,
1807 "Value does not match any choice option",
1808 path,
1809 locator.get_location(path),
1810 ));
1811 return;
1812 }
1813 };
1814
1815 if obj.len() == 1 {
1817 let (tag, value) = obj.iter().next().unwrap();
1818 if let Some(choice_schema) = choices.get(tag) {
1819 let value_path = format!("{}/{}", path, tag);
1821 self.validate_instance(value, choice_schema, root_schema, result, &value_path, locator, depth + 1);
1822 return;
1823 }
1824 }
1825
1826 let mut match_count = 0;
1828
1829 for (_choice_name, choice_schema) in choices {
1830 let mut choice_result = ValidationResult::new();
1831 self.validate_instance(instance, choice_schema, root_schema, &mut choice_result, path, locator, depth + 1);
1832 if choice_result.is_valid() {
1833 match_count += 1;
1834 }
1835 }
1836
1837 if match_count == 0 {
1838 result.add_error(ValidationError::instance_error(
1839 InstanceErrorCode::InstanceChoiceNoMatch,
1840 "Value does not match any choice option",
1841 path,
1842 locator.get_location(path),
1843 ));
1844 } else if match_count > 1 {
1845 result.add_error(ValidationError::instance_error(
1846 InstanceErrorCode::InstanceChoiceMultipleMatches,
1847 format!("Value matches {} choice options (should match exactly one)", match_count),
1848 path,
1849 locator.get_location(path),
1850 ));
1851 }
1852 }
1853 }
1854
1855 fn validate_composition(
1858 &self,
1859 instance: &Value,
1860 schema_obj: &serde_json::Map<String, Value>,
1861 root_schema: &Value,
1862 result: &mut ValidationResult,
1863 path: &str,
1864 locator: &JsonSourceLocator,
1865 depth: usize,
1866 ) {
1867 if let Some(Value::Array(schemas)) = schema_obj.get("allOf") {
1869 for schema in schemas {
1870 let mut sub_result = ValidationResult::new();
1871 self.validate_instance(instance, schema, root_schema, &mut sub_result, path, locator, depth + 1);
1872 if !sub_result.is_valid() {
1873 result.add_error(ValidationError::instance_error(
1874 InstanceErrorCode::InstanceAllOfFailed,
1875 "Value does not match all schemas in allOf",
1876 path,
1877 locator.get_location(path),
1878 ));
1879 result.add_errors(sub_result.all_errors().iter().cloned());
1880 return;
1881 }
1882 }
1883 }
1884
1885 if let Some(Value::Array(schemas)) = schema_obj.get("anyOf") {
1887 let mut any_valid = false;
1888 for schema in schemas {
1889 let mut sub_result = ValidationResult::new();
1890 self.validate_instance(instance, schema, root_schema, &mut sub_result, path, locator, depth + 1);
1891 if sub_result.is_valid() {
1892 any_valid = true;
1893 break;
1894 }
1895 }
1896 if !any_valid {
1897 result.add_error(ValidationError::instance_error(
1898 InstanceErrorCode::InstanceAnyOfFailed,
1899 "Value does not match any schema in anyOf",
1900 path,
1901 locator.get_location(path),
1902 ));
1903 }
1904 }
1905
1906 if let Some(Value::Array(schemas)) = schema_obj.get("oneOf") {
1908 let mut match_count = 0;
1909 for schema in schemas {
1910 let mut sub_result = ValidationResult::new();
1911 self.validate_instance(instance, schema, root_schema, &mut sub_result, path, locator, depth + 1);
1912 if sub_result.is_valid() {
1913 match_count += 1;
1914 }
1915 }
1916 if match_count == 0 {
1917 result.add_error(ValidationError::instance_error(
1918 InstanceErrorCode::InstanceOneOfFailed,
1919 "Value does not match any schema in oneOf",
1920 path,
1921 locator.get_location(path),
1922 ));
1923 } else if match_count > 1 {
1924 result.add_error(ValidationError::instance_error(
1925 InstanceErrorCode::InstanceOneOfMultiple,
1926 format!("Value matches {} schemas in oneOf (should match exactly one)", match_count),
1927 path,
1928 locator.get_location(path),
1929 ));
1930 }
1931 }
1932
1933 if let Some(not_schema) = schema_obj.get("not") {
1935 let mut sub_result = ValidationResult::new();
1936 self.validate_instance(instance, not_schema, root_schema, &mut sub_result, path, locator, depth + 1);
1937 if sub_result.is_valid() {
1938 result.add_error(ValidationError::instance_error(
1939 InstanceErrorCode::InstanceNotFailed,
1940 "Value should not match the schema in 'not'",
1941 path,
1942 locator.get_location(path),
1943 ));
1944 }
1945 }
1946
1947 if let Some(if_schema) = schema_obj.get("if") {
1949 let mut if_result = ValidationResult::new();
1950 self.validate_instance(instance, if_schema, root_schema, &mut if_result, path, locator, depth + 1);
1951
1952 if if_result.is_valid() {
1953 if let Some(then_schema) = schema_obj.get("then") {
1954 self.validate_instance(instance, then_schema, root_schema, result, path, locator, depth + 1);
1955 }
1956 } else if let Some(else_schema) = schema_obj.get("else") {
1957 self.validate_instance(instance, else_schema, root_schema, result, path, locator, depth + 1);
1958 }
1959 }
1960 }
1961}
1962
1963#[cfg(test)]
1964mod tests {
1965 use super::*;
1966
1967 fn make_schema(type_name: &str) -> Value {
1968 serde_json::json!({
1969 "$id": "https://example.com/test",
1970 "name": "Test",
1971 "type": type_name
1972 })
1973 }
1974
1975 #[test]
1976 fn test_string_valid() {
1977 let validator = InstanceValidator::new();
1978 let schema = make_schema("string");
1979 let result = validator.validate(r#""hello""#, &schema);
1980 assert!(result.is_valid());
1981 }
1982
1983 #[test]
1984 fn test_string_invalid() {
1985 let validator = InstanceValidator::new();
1986 let schema = make_schema("string");
1987 let result = validator.validate("123", &schema);
1988 assert!(!result.is_valid());
1989 }
1990
1991 #[test]
1992 fn test_boolean_valid() {
1993 let validator = InstanceValidator::new();
1994 let schema = make_schema("boolean");
1995 let result = validator.validate("true", &schema);
1996 assert!(result.is_valid());
1997 }
1998
1999 #[test]
2000 fn test_int32_valid() {
2001 let validator = InstanceValidator::new();
2002 let schema = make_schema("int32");
2003 let result = validator.validate("42", &schema);
2004 assert!(result.is_valid());
2005 }
2006
2007 #[test]
2008 fn test_object_valid() {
2009 let validator = InstanceValidator::new();
2010 let schema = serde_json::json!({
2011 "$id": "https://example.com/test",
2012 "name": "Test",
2013 "type": "object",
2014 "properties": {
2015 "name": { "type": "string" }
2016 },
2017 "required": ["name"]
2018 });
2019 let result = validator.validate(r#"{"name": "test"}"#, &schema);
2020 assert!(result.is_valid());
2021 }
2022
2023 #[test]
2024 fn test_object_missing_required() {
2025 let validator = InstanceValidator::new();
2026 let schema = serde_json::json!({
2027 "$id": "https://example.com/test",
2028 "name": "Test",
2029 "type": "object",
2030 "properties": {
2031 "name": { "type": "string" }
2032 },
2033 "required": ["name"]
2034 });
2035 let result = validator.validate(r#"{}"#, &schema);
2036 assert!(!result.is_valid());
2037 }
2038
2039 #[test]
2040 fn test_array_valid() {
2041 let validator = InstanceValidator::new();
2042 let schema = serde_json::json!({
2043 "$id": "https://example.com/test",
2044 "name": "Test",
2045 "type": "array",
2046 "items": { "type": "int32" }
2047 });
2048 let result = validator.validate("[1, 2, 3]", &schema);
2049 assert!(result.is_valid());
2050 }
2051
2052 #[test]
2053 fn test_enum_valid() {
2054 let validator = InstanceValidator::new();
2055 let schema = serde_json::json!({
2056 "$id": "https://example.com/test",
2057 "name": "Test",
2058 "type": "string",
2059 "enum": ["a", "b", "c"]
2060 });
2061 let result = validator.validate(r#""b""#, &schema);
2062 assert!(result.is_valid());
2063 }
2064
2065 #[test]
2066 fn test_enum_invalid() {
2067 let validator = InstanceValidator::new();
2068 let schema = serde_json::json!({
2069 "$id": "https://example.com/test",
2070 "name": "Test",
2071 "type": "string",
2072 "enum": ["a", "b", "c"]
2073 });
2074 let result = validator.validate(r#""d""#, &schema);
2075 assert!(!result.is_valid());
2076 }
2077}