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 let merged_schema;
145 let effective_schema_obj = if schema_obj.contains_key("$extends") {
146 if let Some(merged) = self.merge_extends(schema_obj, root_schema) {
147 merged_schema = merged;
148 &merged_schema
149 } else {
150 schema_obj
151 }
152 } else {
153 schema_obj
154 };
155
156 if let Some(ref_val) = effective_schema_obj.get("$ref") {
158 if let Value::String(ref_str) = ref_val {
159 if let Some(resolved) = self.resolve_ref(ref_str, root_schema) {
160 self.validate_instance(instance, resolved, root_schema, result, path, locator, depth + 1);
161 return;
162 } else {
163 result.add_error(ValidationError::instance_error(
164 InstanceErrorCode::InstanceRefNotFound,
165 format!("Reference not found: {}", ref_str),
166 path,
167 locator.get_location(path),
168 ));
169 return;
170 }
171 }
172 }
173
174 if let Some(enum_val) = effective_schema_obj.get("enum") {
176 if !self.validate_enum(instance, enum_val) {
177 result.add_error(ValidationError::instance_error(
178 InstanceErrorCode::InstanceEnumMismatch,
179 "Value does not match any enum value",
180 path,
181 locator.get_location(path),
182 ));
183 return;
184 }
185 }
186
187 if let Some(const_val) = effective_schema_obj.get("const") {
189 if instance != const_val {
190 result.add_error(ValidationError::instance_error(
191 InstanceErrorCode::InstanceConstMismatch,
192 "Value does not match const",
193 path,
194 locator.get_location(path),
195 ));
196 return;
197 }
198 }
199
200 if let Some(type_val) = effective_schema_obj.get("type") {
202 match type_val {
203 Value::String(type_name) => {
204 self.validate_type(instance, type_name, effective_schema_obj, root_schema, result, path, locator, depth);
205 }
206 Value::Array(types) => {
207 let mut union_valid = false;
209 for t in types {
210 match t {
211 Value::String(type_name) => {
212 let mut temp_result = ValidationResult::new();
213 self.validate_type(instance, type_name, effective_schema_obj, root_schema, &mut temp_result, path, locator, depth);
214 if temp_result.is_valid() {
215 union_valid = true;
216 break;
217 }
218 }
219 Value::Object(ref_obj) => {
220 if let Some(Value::String(ref_str)) = ref_obj.get("$ref") {
221 if let Some(resolved) = self.resolve_ref(ref_str, root_schema) {
222 let mut temp_result = ValidationResult::new();
223 self.validate_instance(instance, resolved, root_schema, &mut temp_result, path, locator, depth + 1);
224 if temp_result.is_valid() {
225 union_valid = true;
226 break;
227 }
228 }
229 }
230 }
231 _ => {}
232 }
233 }
234 if !union_valid {
235 result.add_error(ValidationError::instance_error(
236 InstanceErrorCode::InstanceUnionNoMatch,
237 "Value does not match any type in union",
238 path,
239 locator.get_location(path),
240 ));
241 }
242 return;
243 }
244 Value::Object(ref_obj) => {
245 if let Some(Value::String(ref_str)) = ref_obj.get("$ref") {
247 if let Some(resolved) = self.resolve_ref(ref_str, root_schema) {
248 self.validate_instance(instance, resolved, root_schema, result, path, locator, depth + 1);
249 } else {
250 result.add_error(ValidationError::instance_error(
251 InstanceErrorCode::InstanceRefNotFound,
252 format!("Reference not found: {}", ref_str),
253 path,
254 locator.get_location(path),
255 ));
256 }
257 }
258 return;
259 }
260 _ => {}
261 }
262 }
263
264 if self.options.extended {
266 self.validate_composition(instance, effective_schema_obj, root_schema, result, path, locator, depth);
267 }
268 }
269
270 fn resolve_ref<'a>(&self, ref_str: &str, root_schema: &'a Value) -> Option<&'a Value> {
272 if let Some(def_path) = ref_str.strip_prefix("#/definitions/") {
273 let parts: Vec<&str> = def_path.split('/').collect();
275 let mut current = root_schema.get("definitions")?;
276
277 for part in parts {
278 if let Some(next) = current.get(part) {
280 current = next;
281 } else if let Some(defs) = current.get("definitions") {
282 current = defs.get(part)?;
284 } else {
285 return None;
286 }
287 }
288 Some(current)
289 } else {
290 None
291 }
292 }
293
294 fn merge_extends(&self, schema_obj: &serde_json::Map<String, Value>, root_schema: &Value) -> Option<serde_json::Map<String, Value>> {
296 let extends_val = schema_obj.get("$extends")?;
297
298 let refs: Vec<&str> = match extends_val {
300 Value::String(s) => vec![s.as_str()],
301 Value::Array(arr) => arr.iter()
302 .filter_map(|v| v.as_str())
303 .collect(),
304 _ => return None,
305 };
306
307 if refs.is_empty() {
308 return None;
309 }
310
311 let mut merged_properties = serde_json::Map::new();
313 let mut merged_required: Vec<Value> = Vec::new();
314
315 for ref_str in refs {
316 if let Some(base_schema) = self.resolve_ref(ref_str, root_schema) {
317 if let Some(base_obj) = base_schema.as_object() {
318 if let Some(Value::Object(base_props)) = base_obj.get("properties") {
320 for (key, value) in base_props {
321 if !merged_properties.contains_key(key) {
322 merged_properties.insert(key.clone(), value.clone());
323 }
324 }
325 }
326 if let Some(Value::Array(base_req)) = base_obj.get("required") {
328 for r in base_req {
329 if let Value::String(s) = r {
330 if !merged_required.iter().any(|x| x.as_str() == Some(s.as_str())) {
331 merged_required.push(r.clone());
332 }
333 }
334 }
335 }
336 }
337 }
338 }
339
340 if let Some(Value::Object(props)) = schema_obj.get("properties") {
342 for (key, value) in props {
343 merged_properties.insert(key.clone(), value.clone());
344 }
345 }
346 if let Some(Value::Array(req)) = schema_obj.get("required") {
347 for r in req {
348 if let Value::String(s) = r {
349 if !merged_required.iter().any(|x| x.as_str() == Some(s.as_str())) {
350 merged_required.push(r.clone());
351 }
352 }
353 }
354 }
355
356 let mut merged = schema_obj.clone();
358 merged.remove("$extends");
359 if !merged_properties.is_empty() {
360 merged.insert("properties".to_string(), Value::Object(merged_properties));
361 }
362 if !merged_required.is_empty() {
363 merged.insert("required".to_string(), Value::Array(merged_required));
364 }
365
366 Some(merged)
367 }
368
369 fn validate_enum(&self, instance: &Value, enum_val: &Value) -> bool {
371 if let Value::Array(arr) = enum_val {
372 arr.iter().any(|v| v == instance)
373 } else {
374 false
375 }
376 }
377
378 fn validate_type(
380 &self,
381 instance: &Value,
382 type_name: &str,
383 schema_obj: &serde_json::Map<String, Value>,
384 root_schema: &Value,
385 result: &mut ValidationResult,
386 path: &str,
387 locator: &JsonSourceLocator,
388 depth: usize,
389 ) {
390 match type_name {
391 "string" => self.validate_string(instance, schema_obj, result, path, locator),
392 "boolean" => self.validate_boolean(instance, result, path, locator),
393 "null" => self.validate_null(instance, result, path, locator),
394 "number" => self.validate_number(instance, schema_obj, result, path, locator),
395 "integer" | "int32" => self.validate_int32(instance, schema_obj, result, path, locator),
396 "int8" => self.validate_int_range(instance, schema_obj, result, path, locator, -128, 127, "int8"),
397 "int16" => self.validate_int_range(instance, schema_obj, result, path, locator, -32768, 32767, "int16"),
398 "int64" => self.validate_int64(instance, schema_obj, result, path, locator),
399 "int128" => self.validate_int128(instance, schema_obj, result, path, locator),
400 "uint8" => self.validate_uint_range(instance, schema_obj, result, path, locator, 0, 255, "uint8"),
401 "uint16" => self.validate_uint_range(instance, schema_obj, result, path, locator, 0, 65535, "uint16"),
402 "uint32" => self.validate_uint32(instance, schema_obj, result, path, locator),
403 "uint64" => self.validate_uint64(instance, schema_obj, result, path, locator),
404 "uint128" => self.validate_uint128(instance, schema_obj, result, path, locator),
405 "float" | "float8" | "double" => {
406 self.validate_number(instance, schema_obj, result, path, locator)
407 }
408 "decimal" => self.validate_decimal(instance, schema_obj, result, path, locator),
409 "date" => self.validate_date(instance, result, path, locator),
410 "time" => self.validate_time(instance, result, path, locator),
411 "datetime" => self.validate_datetime(instance, result, path, locator),
412 "duration" => self.validate_duration(instance, result, path, locator),
413 "uuid" => self.validate_uuid(instance, result, path, locator),
414 "uri" => self.validate_uri(instance, result, path, locator),
415 "binary" => self.validate_binary(instance, result, path, locator),
416 "jsonpointer" => self.validate_jsonpointer(instance, result, path, locator),
417 "object" => self.validate_object(instance, schema_obj, root_schema, result, path, locator, depth),
418 "array" => self.validate_array(instance, schema_obj, root_schema, result, path, locator, depth),
419 "set" => self.validate_set(instance, schema_obj, root_schema, result, path, locator, depth),
420 "map" => self.validate_map(instance, schema_obj, root_schema, result, path, locator, depth),
421 "tuple" => self.validate_tuple(instance, schema_obj, root_schema, result, path, locator, depth),
422 "choice" => self.validate_choice(instance, schema_obj, root_schema, result, path, locator, depth),
423 "any" => {} _ => {
425 result.add_error(ValidationError::instance_error(
426 InstanceErrorCode::InstanceTypeUnknown,
427 format!("Unknown type: {}", type_name),
428 path,
429 locator.get_location(path),
430 ));
431 }
432 }
433 }
434
435 fn validate_string(
438 &self,
439 instance: &Value,
440 schema_obj: &serde_json::Map<String, Value>,
441 result: &mut ValidationResult,
442 path: &str,
443 locator: &JsonSourceLocator,
444 ) {
445 let s = match instance {
446 Value::String(s) => s,
447 _ => {
448 result.add_error(ValidationError::instance_error(
449 InstanceErrorCode::InstanceStringExpected,
450 "Expected string",
451 path,
452 locator.get_location(path),
453 ));
454 return;
455 }
456 };
457
458 if self.options.extended {
459 if let Some(Value::Number(n)) = schema_obj.get("minLength") {
461 if let Some(min) = n.as_u64() {
462 if s.chars().count() < min as usize {
463 result.add_error(ValidationError::instance_error(
464 InstanceErrorCode::InstanceStringTooShort,
465 format!("String length {} is less than minimum {}", s.chars().count(), min),
466 path,
467 locator.get_location(path),
468 ));
469 }
470 }
471 }
472
473 if let Some(Value::Number(n)) = schema_obj.get("maxLength") {
475 if let Some(max) = n.as_u64() {
476 if s.chars().count() > max as usize {
477 result.add_error(ValidationError::instance_error(
478 InstanceErrorCode::InstanceStringTooLong,
479 format!("String length {} is greater than maximum {}", s.chars().count(), max),
480 path,
481 locator.get_location(path),
482 ));
483 }
484 }
485 }
486
487 if let Some(Value::String(pattern)) = schema_obj.get("pattern") {
489 if let Ok(re) = Regex::new(pattern) {
490 if !re.is_match(s) {
491 result.add_error(ValidationError::instance_error(
492 InstanceErrorCode::InstanceStringPatternMismatch,
493 format!("String does not match pattern: {}", pattern),
494 path,
495 locator.get_location(path),
496 ));
497 }
498 }
499 }
500
501 if let Some(Value::String(fmt)) = schema_obj.get("format") {
503 self.validate_string_format(s, fmt, result, path, locator);
504 }
505 }
506 }
507
508 fn validate_string_format(
510 &self,
511 s: &str,
512 fmt: &str,
513 result: &mut ValidationResult,
514 path: &str,
515 locator: &JsonSourceLocator,
516 ) {
517 let valid = match fmt {
518 "email" => self.validate_email_format(s),
519 "ipv4" => self.validate_ipv4_format(s),
520 "ipv6" => self.validate_ipv6_format(s),
521 "hostname" => self.validate_hostname_format(s),
522 "uri" => self.validate_uri_format(s),
523 "uri-reference" => self.validate_uri_reference_format(s),
524 "idn-email" => self.validate_idn_email_format(s),
525 "idn-hostname" => self.validate_idn_hostname_format(s),
526 "iri" | "iri-reference" => true, "uri-template" | "regex" | "relative-json-pointer" => true, _ => true, };
530
531 if !valid {
532 result.add_error(ValidationError::instance_error(
533 InstanceErrorCode::InstanceStringFormatInvalid,
534 format!("String does not match format: {}", fmt),
535 path,
536 locator.get_location(path),
537 ));
538 }
539 }
540
541 fn validate_email_format(&self, s: &str) -> bool {
543 if let Some(at_pos) = s.find('@') {
545 let local = &s[..at_pos];
546 let domain = &s[at_pos + 1..];
547 !local.is_empty() && !domain.is_empty() && domain.contains('.') &&
549 !domain.starts_with('.') && !domain.ends_with('.')
550 } else {
551 false
552 }
553 }
554
555 fn validate_ipv4_format(&self, s: &str) -> bool {
557 let parts: Vec<&str> = s.split('.').collect();
558 if parts.len() != 4 {
559 return false;
560 }
561 for part in parts {
562 if part.is_empty() || part.len() > 3 {
563 return false;
564 }
565 if part.len() > 1 && part.starts_with('0') {
567 return false;
568 }
569 match part.parse::<u32>() {
570 Ok(n) if n <= 255 => continue,
571 _ => return false,
572 }
573 }
574 true
575 }
576
577 fn validate_ipv6_format(&self, s: &str) -> bool {
579 if s.contains('.') {
581 if let Some(last_colon) = s.rfind(':') {
583 let ipv4_part = &s[last_colon + 1..];
584 if !self.validate_ipv4_format(ipv4_part) {
585 return false;
586 }
587 }
588 }
589
590 let mut colon_count = 0;
592 let mut has_double_colon = false;
593
594 for (i, c) in s.chars().enumerate() {
595 if c == ':' {
596 colon_count += 1;
597 if i > 0 && s.chars().nth(i - 1) == Some(':') {
598 if has_double_colon {
599 return false; }
601 has_double_colon = true;
602 }
603 } else if !c.is_ascii_hexdigit() && c != '.' {
604 return false;
605 }
606 }
607
608 (2..=7).contains(&colon_count)
610 }
611
612 fn validate_hostname_format(&self, s: &str) -> bool {
614 if s.is_empty() || s.len() > 253 {
615 return false;
616 }
617
618 for label in s.split('.') {
619 if label.is_empty() || label.len() > 63 {
620 return false;
621 }
622 if !label.chars().next().map(|c| c.is_ascii_alphanumeric()).unwrap_or(false) {
624 return false;
625 }
626 if !label.chars().last().map(|c| c.is_ascii_alphanumeric()).unwrap_or(false) {
628 return false;
629 }
630 for c in label.chars() {
632 if !c.is_ascii_alphanumeric() && c != '-' {
633 return false;
634 }
635 }
636 }
637 true
638 }
639
640 fn validate_uri_format(&self, s: &str) -> bool {
642 url::Url::parse(s).is_ok()
644 }
645
646 fn validate_uri_reference_format(&self, s: &str) -> bool {
648 if url::Url::parse(s).is_ok() {
651 return true;
652 }
653 !s.contains(' ') && !s.contains('\n') && !s.contains('\r')
655 }
656
657 fn validate_idn_email_format(&self, s: &str) -> bool {
659 s.contains('@') && !s.starts_with('@') && !s.ends_with('@')
661 }
662
663 fn validate_idn_hostname_format(&self, s: &str) -> bool {
665 !s.is_empty() && s.len() <= 253 && !s.contains(' ')
667 }
668
669 fn validate_boolean(
670 &self,
671 instance: &Value,
672 result: &mut ValidationResult,
673 path: &str,
674 locator: &JsonSourceLocator,
675 ) {
676 if !instance.is_boolean() {
677 result.add_error(ValidationError::instance_error(
678 InstanceErrorCode::InstanceBooleanExpected,
679 "Expected boolean",
680 path,
681 locator.get_location(path),
682 ));
683 }
684 }
685
686 fn validate_null(
687 &self,
688 instance: &Value,
689 result: &mut ValidationResult,
690 path: &str,
691 locator: &JsonSourceLocator,
692 ) {
693 if !instance.is_null() {
694 result.add_error(ValidationError::instance_error(
695 InstanceErrorCode::InstanceNullExpected,
696 "Expected null",
697 path,
698 locator.get_location(path),
699 ));
700 }
701 }
702
703 fn validate_number(
704 &self,
705 instance: &Value,
706 schema_obj: &serde_json::Map<String, Value>,
707 result: &mut ValidationResult,
708 path: &str,
709 locator: &JsonSourceLocator,
710 ) {
711 let num = match instance {
712 Value::Number(n) => n,
713 _ => {
714 result.add_error(ValidationError::instance_error(
715 InstanceErrorCode::InstanceNumberExpected,
716 "Expected number",
717 path,
718 locator.get_location(path),
719 ));
720 return;
721 }
722 };
723
724 if self.options.extended {
725 let value = num.as_f64().unwrap_or(0.0);
726
727 if let Some(Value::Number(n)) = schema_obj.get("minimum") {
729 if let Some(min) = n.as_f64() {
730 if value < min {
731 result.add_error(ValidationError::instance_error(
732 InstanceErrorCode::InstanceNumberTooSmall,
733 format!("Value {} is less than minimum {}", value, min),
734 path,
735 locator.get_location(path),
736 ));
737 }
738 }
739 }
740
741 if let Some(Value::Number(n)) = schema_obj.get("maximum") {
743 if let Some(max) = n.as_f64() {
744 if value > max {
745 result.add_error(ValidationError::instance_error(
746 InstanceErrorCode::InstanceNumberTooLarge,
747 format!("Value {} is greater than maximum {}", value, max),
748 path,
749 locator.get_location(path),
750 ));
751 }
752 }
753 }
754
755 if let Some(Value::Number(n)) = schema_obj.get("exclusiveMinimum") {
757 if let Some(min) = n.as_f64() {
758 if value <= min {
759 result.add_error(ValidationError::instance_error(
760 InstanceErrorCode::InstanceNumberTooSmall,
761 format!("Value {} is not greater than exclusive minimum {}", value, min),
762 path,
763 locator.get_location(path),
764 ));
765 }
766 }
767 }
768
769 if let Some(Value::Number(n)) = schema_obj.get("exclusiveMaximum") {
771 if let Some(max) = n.as_f64() {
772 if value >= max {
773 result.add_error(ValidationError::instance_error(
774 InstanceErrorCode::InstanceNumberTooLarge,
775 format!("Value {} is not less than exclusive maximum {}", value, max),
776 path,
777 locator.get_location(path),
778 ));
779 }
780 }
781 }
782 }
783 }
784
785 fn validate_decimal(
789 &self,
790 instance: &Value,
791 schema_obj: &serde_json::Map<String, Value>,
792 result: &mut ValidationResult,
793 path: &str,
794 locator: &JsonSourceLocator,
795 ) {
796 let value: f64 = match instance {
797 Value::String(s) => {
798 match s.parse::<f64>() {
800 Ok(v) => v,
801 Err(_) => {
802 result.add_error(ValidationError::instance_error(
803 InstanceErrorCode::InstanceDecimalExpected,
804 format!("Invalid decimal format: {}", s),
805 path,
806 locator.get_location(path),
807 ));
808 return;
809 }
810 }
811 }
812 Value::Number(n) => {
813 n.as_f64().unwrap_or(0.0)
815 }
816 _ => {
817 result.add_error(ValidationError::instance_error(
818 InstanceErrorCode::InstanceDecimalExpected,
819 "Expected decimal (as string or number)",
820 path,
821 locator.get_location(path),
822 ));
823 return;
824 }
825 };
826
827 if self.options.extended {
829 if let Some(min_val) = schema_obj.get("minimum") {
831 let min = match min_val {
832 Value::Number(n) => n.as_f64(),
833 Value::String(s) => s.parse::<f64>().ok(),
834 _ => None,
835 };
836 if let Some(min) = min {
837 if value < min {
838 result.add_error(ValidationError::instance_error(
839 InstanceErrorCode::InstanceNumberTooSmall,
840 format!("Value {} is less than minimum {}", value, min),
841 path,
842 locator.get_location(path),
843 ));
844 }
845 }
846 }
847
848 if let Some(max_val) = schema_obj.get("maximum") {
850 let max = match max_val {
851 Value::Number(n) => n.as_f64(),
852 Value::String(s) => s.parse::<f64>().ok(),
853 _ => None,
854 };
855 if let Some(max) = max {
856 if value > max {
857 result.add_error(ValidationError::instance_error(
858 InstanceErrorCode::InstanceNumberTooLarge,
859 format!("Value {} is greater than maximum {}", value, max),
860 path,
861 locator.get_location(path),
862 ));
863 }
864 }
865 }
866
867 if let Some(min_val) = schema_obj.get("exclusiveMinimum") {
869 let min = match min_val {
870 Value::Number(n) => n.as_f64(),
871 Value::String(s) => s.parse::<f64>().ok(),
872 _ => None,
873 };
874 if let Some(min) = min {
875 if value <= min {
876 result.add_error(ValidationError::instance_error(
877 InstanceErrorCode::InstanceNumberTooSmall,
878 format!("Value {} is not greater than exclusive minimum {}", value, min),
879 path,
880 locator.get_location(path),
881 ));
882 }
883 }
884 }
885
886 if let Some(max_val) = schema_obj.get("exclusiveMaximum") {
888 let max = match max_val {
889 Value::Number(n) => n.as_f64(),
890 Value::String(s) => s.parse::<f64>().ok(),
891 _ => None,
892 };
893 if let Some(max) = max {
894 if value >= max {
895 result.add_error(ValidationError::instance_error(
896 InstanceErrorCode::InstanceNumberTooLarge,
897 format!("Value {} is not less than exclusive maximum {}", value, max),
898 path,
899 locator.get_location(path),
900 ));
901 }
902 }
903 }
904 }
905 }
906
907 fn validate_int32(
908 &self,
909 instance: &Value,
910 _schema_obj: &serde_json::Map<String, Value>,
911 result: &mut ValidationResult,
912 path: &str,
913 locator: &JsonSourceLocator,
914 ) {
915 self.validate_int_range(instance, _schema_obj, result, path, locator, i32::MIN as i64, i32::MAX as i64, "int32")
916 }
917
918 fn validate_int_range(
919 &self,
920 instance: &Value,
921 schema_obj: &serde_json::Map<String, Value>,
922 result: &mut ValidationResult,
923 path: &str,
924 locator: &JsonSourceLocator,
925 min: i64,
926 max: i64,
927 type_name: &str,
928 ) {
929 let num = match instance {
930 Value::Number(n) => n,
931 _ => {
932 result.add_error(ValidationError::instance_error(
933 InstanceErrorCode::InstanceIntegerExpected,
934 format!("Expected {}", type_name),
935 path,
936 locator.get_location(path),
937 ));
938 return;
939 }
940 };
941
942 let val = if let Some(v) = num.as_i64() {
943 if v < min || v > max {
944 result.add_error(ValidationError::instance_error(
945 InstanceErrorCode::InstanceIntegerOutOfRange,
946 format!("Value {} is out of range for {} ({} to {})", v, type_name, min, max),
947 path,
948 locator.get_location(path),
949 ));
950 }
951 v as f64
952 } else if let Some(v) = num.as_f64() {
953 if v.fract() != 0.0 {
954 result.add_error(ValidationError::instance_error(
955 InstanceErrorCode::InstanceIntegerExpected,
956 format!("Expected integer, got {}", v),
957 path,
958 locator.get_location(path),
959 ));
960 return;
961 }
962 if v < min as f64 || v > max as f64 {
963 result.add_error(ValidationError::instance_error(
964 InstanceErrorCode::InstanceIntegerOutOfRange,
965 format!("Value {} is out of range for {}", v, type_name),
966 path,
967 locator.get_location(path),
968 ));
969 }
970 v
971 } else {
972 return;
973 };
974
975 if self.options.extended {
977 if let Some(Value::Number(n)) = schema_obj.get("minimum") {
979 if let Some(min_val) = n.as_f64() {
980 if val < min_val {
981 result.add_error(ValidationError::instance_error(
982 InstanceErrorCode::InstanceNumberTooSmall,
983 format!("Value {} is less than minimum {}", val, min_val),
984 path,
985 locator.get_location(path),
986 ));
987 }
988 }
989 }
990
991 if let Some(Value::Number(n)) = schema_obj.get("maximum") {
993 if let Some(max_val) = n.as_f64() {
994 if val > max_val {
995 result.add_error(ValidationError::instance_error(
996 InstanceErrorCode::InstanceNumberTooLarge,
997 format!("Value {} is greater than maximum {}", val, max_val),
998 path,
999 locator.get_location(path),
1000 ));
1001 }
1002 }
1003 }
1004
1005 if let Some(Value::Number(n)) = schema_obj.get("exclusiveMinimum") {
1007 if let Some(min_val) = n.as_f64() {
1008 if val <= min_val {
1009 result.add_error(ValidationError::instance_error(
1010 InstanceErrorCode::InstanceNumberTooSmall,
1011 format!("Value {} is not greater than exclusive minimum {}", val, min_val),
1012 path,
1013 locator.get_location(path),
1014 ));
1015 }
1016 }
1017 }
1018
1019 if let Some(Value::Number(n)) = schema_obj.get("exclusiveMaximum") {
1021 if let Some(max_val) = n.as_f64() {
1022 if val >= max_val {
1023 result.add_error(ValidationError::instance_error(
1024 InstanceErrorCode::InstanceNumberTooLarge,
1025 format!("Value {} is not less than exclusive maximum {}", val, max_val),
1026 path,
1027 locator.get_location(path),
1028 ));
1029 }
1030 }
1031 }
1032
1033 if let Some(Value::Number(n)) = schema_obj.get("multipleOf") {
1035 if let Some(mul) = n.as_f64() {
1036 if mul > 0.0 && (val % mul).abs() > f64::EPSILON {
1037 result.add_error(ValidationError::instance_error(
1038 InstanceErrorCode::InstanceNumberNotMultiple,
1039 format!("Value {} is not a multiple of {}", val, mul),
1040 path,
1041 locator.get_location(path),
1042 ));
1043 }
1044 }
1045 }
1046 }
1047 }
1048
1049 fn validate_uint_range(
1050 &self,
1051 instance: &Value,
1052 schema_obj: &serde_json::Map<String, Value>,
1053 result: &mut ValidationResult,
1054 path: &str,
1055 locator: &JsonSourceLocator,
1056 min: u64,
1057 max: u64,
1058 type_name: &str,
1059 ) {
1060 let num = match instance {
1061 Value::Number(n) => n,
1062 _ => {
1063 result.add_error(ValidationError::instance_error(
1064 InstanceErrorCode::InstanceIntegerExpected,
1065 format!("Expected {}", type_name),
1066 path,
1067 locator.get_location(path),
1068 ));
1069 return;
1070 }
1071 };
1072
1073 if let Some(val) = num.as_u64() {
1074 if val < min || val > max {
1075 result.add_error(ValidationError::instance_error(
1076 InstanceErrorCode::InstanceIntegerOutOfRange,
1077 format!("Value {} is out of range for {} ({} to {})", val, type_name, min, max),
1078 path,
1079 locator.get_location(path),
1080 ));
1081 }
1082 } else if let Some(v) = num.as_i64() {
1083 if v < 0 {
1084 result.add_error(ValidationError::instance_error(
1085 InstanceErrorCode::InstanceIntegerOutOfRange,
1086 format!("Value {} is negative, expected unsigned {}", v, type_name),
1087 path,
1088 locator.get_location(path),
1089 ));
1090 return;
1091 }
1092 } else {
1093 return;
1094 }
1095
1096 let val = num.as_f64().unwrap_or(0.0);
1098
1099 if self.options.extended {
1101 if let Some(Value::Number(n)) = schema_obj.get("minimum") {
1103 if let Some(min_val) = n.as_f64() {
1104 if val < min_val {
1105 result.add_error(ValidationError::instance_error(
1106 InstanceErrorCode::InstanceNumberTooSmall,
1107 format!("Value {} is less than minimum {}", val, min_val),
1108 path,
1109 locator.get_location(path),
1110 ));
1111 }
1112 }
1113 }
1114
1115 if let Some(Value::Number(n)) = schema_obj.get("maximum") {
1117 if let Some(max_val) = n.as_f64() {
1118 if val > max_val {
1119 result.add_error(ValidationError::instance_error(
1120 InstanceErrorCode::InstanceNumberTooLarge,
1121 format!("Value {} is greater than maximum {}", val, max_val),
1122 path,
1123 locator.get_location(path),
1124 ));
1125 }
1126 }
1127 }
1128
1129 if let Some(Value::Number(n)) = schema_obj.get("exclusiveMinimum") {
1131 if let Some(min_val) = n.as_f64() {
1132 if val <= min_val {
1133 result.add_error(ValidationError::instance_error(
1134 InstanceErrorCode::InstanceNumberTooSmall,
1135 format!("Value {} is not greater than exclusive minimum {}", val, min_val),
1136 path,
1137 locator.get_location(path),
1138 ));
1139 }
1140 }
1141 }
1142
1143 if let Some(Value::Number(n)) = schema_obj.get("exclusiveMaximum") {
1145 if let Some(max_val) = n.as_f64() {
1146 if val >= max_val {
1147 result.add_error(ValidationError::instance_error(
1148 InstanceErrorCode::InstanceNumberTooLarge,
1149 format!("Value {} is not less than exclusive maximum {}", val, max_val),
1150 path,
1151 locator.get_location(path),
1152 ));
1153 }
1154 }
1155 }
1156
1157 if let Some(Value::Number(n)) = schema_obj.get("multipleOf") {
1159 if let Some(mul) = n.as_f64() {
1160 if mul > 0.0 && (val % mul).abs() > f64::EPSILON {
1161 result.add_error(ValidationError::instance_error(
1162 InstanceErrorCode::InstanceNumberNotMultiple,
1163 format!("Value {} is not a multiple of {}", val, mul),
1164 path,
1165 locator.get_location(path),
1166 ));
1167 }
1168 }
1169 }
1170 }
1171 }
1172
1173 fn validate_int64(
1174 &self,
1175 instance: &Value,
1176 _schema_obj: &serde_json::Map<String, Value>,
1177 result: &mut ValidationResult,
1178 path: &str,
1179 locator: &JsonSourceLocator,
1180 ) {
1181 match instance {
1182 Value::Number(n) => {
1183 if n.as_i64().is_none() && n.as_f64().is_none_or(|f| f.fract() != 0.0) {
1184 result.add_error(ValidationError::instance_error(
1185 InstanceErrorCode::InstanceIntegerExpected,
1186 "Expected int64",
1187 path,
1188 locator.get_location(path),
1189 ));
1190 }
1191 }
1192 Value::String(s) => {
1193 if s.parse::<i64>().is_err() {
1195 result.add_error(ValidationError::instance_error(
1196 InstanceErrorCode::InstanceIntegerExpected,
1197 "Expected int64 (as number or string)",
1198 path,
1199 locator.get_location(path),
1200 ));
1201 }
1202 }
1203 _ => {
1204 result.add_error(ValidationError::instance_error(
1205 InstanceErrorCode::InstanceIntegerExpected,
1206 "Expected int64",
1207 path,
1208 locator.get_location(path),
1209 ));
1210 }
1211 }
1212 }
1213
1214 fn validate_int128(
1215 &self,
1216 instance: &Value,
1217 _schema_obj: &serde_json::Map<String, Value>,
1218 result: &mut ValidationResult,
1219 path: &str,
1220 locator: &JsonSourceLocator,
1221 ) {
1222 match instance {
1223 Value::Number(_) => {} Value::String(s) => {
1225 if s.parse::<i128>().is_err() {
1226 result.add_error(ValidationError::instance_error(
1227 InstanceErrorCode::InstanceIntegerExpected,
1228 "Expected int128 (as number or string)",
1229 path,
1230 locator.get_location(path),
1231 ));
1232 }
1233 }
1234 _ => {
1235 result.add_error(ValidationError::instance_error(
1236 InstanceErrorCode::InstanceIntegerExpected,
1237 "Expected int128",
1238 path,
1239 locator.get_location(path),
1240 ));
1241 }
1242 }
1243 }
1244
1245 fn validate_uint32(
1246 &self,
1247 instance: &Value,
1248 _schema_obj: &serde_json::Map<String, Value>,
1249 result: &mut ValidationResult,
1250 path: &str,
1251 locator: &JsonSourceLocator,
1252 ) {
1253 self.validate_uint_range(instance, _schema_obj, result, path, locator, 0, u32::MAX as u64, "uint32")
1254 }
1255
1256 fn validate_uint64(
1257 &self,
1258 instance: &Value,
1259 _schema_obj: &serde_json::Map<String, Value>,
1260 result: &mut ValidationResult,
1261 path: &str,
1262 locator: &JsonSourceLocator,
1263 ) {
1264 match instance {
1265 Value::Number(n) => {
1266 if n.as_u64().is_none() {
1267 if let Some(i) = n.as_i64() {
1268 if i < 0 {
1269 result.add_error(ValidationError::instance_error(
1270 InstanceErrorCode::InstanceIntegerOutOfRange,
1271 "Expected unsigned uint64",
1272 path,
1273 locator.get_location(path),
1274 ));
1275 }
1276 }
1277 }
1278 }
1279 Value::String(s) => {
1280 if s.parse::<u64>().is_err() {
1281 result.add_error(ValidationError::instance_error(
1282 InstanceErrorCode::InstanceIntegerExpected,
1283 "Expected uint64 (as number or string)",
1284 path,
1285 locator.get_location(path),
1286 ));
1287 }
1288 }
1289 _ => {
1290 result.add_error(ValidationError::instance_error(
1291 InstanceErrorCode::InstanceIntegerExpected,
1292 "Expected uint64",
1293 path,
1294 locator.get_location(path),
1295 ));
1296 }
1297 }
1298 }
1299
1300 fn validate_uint128(
1301 &self,
1302 instance: &Value,
1303 _schema_obj: &serde_json::Map<String, Value>,
1304 result: &mut ValidationResult,
1305 path: &str,
1306 locator: &JsonSourceLocator,
1307 ) {
1308 match instance {
1309 Value::Number(n) => {
1310 if let Some(i) = n.as_i64() {
1311 if i < 0 {
1312 result.add_error(ValidationError::instance_error(
1313 InstanceErrorCode::InstanceIntegerOutOfRange,
1314 "Expected unsigned uint128",
1315 path,
1316 locator.get_location(path),
1317 ));
1318 }
1319 }
1320 }
1321 Value::String(s) => {
1322 if s.parse::<u128>().is_err() {
1323 result.add_error(ValidationError::instance_error(
1324 InstanceErrorCode::InstanceIntegerExpected,
1325 "Expected uint128 (as number or string)",
1326 path,
1327 locator.get_location(path),
1328 ));
1329 }
1330 }
1331 _ => {
1332 result.add_error(ValidationError::instance_error(
1333 InstanceErrorCode::InstanceIntegerExpected,
1334 "Expected uint128",
1335 path,
1336 locator.get_location(path),
1337 ));
1338 }
1339 }
1340 }
1341
1342 fn validate_date(
1345 &self,
1346 instance: &Value,
1347 result: &mut ValidationResult,
1348 path: &str,
1349 locator: &JsonSourceLocator,
1350 ) {
1351 let s = match instance {
1352 Value::String(s) => s,
1353 _ => {
1354 result.add_error(ValidationError::instance_error(
1355 InstanceErrorCode::InstanceDateExpected,
1356 "Expected date string",
1357 path,
1358 locator.get_location(path),
1359 ));
1360 return;
1361 }
1362 };
1363
1364 if NaiveDate::parse_from_str(s, "%Y-%m-%d").is_err() {
1365 result.add_error(ValidationError::instance_error(
1366 InstanceErrorCode::InstanceDateInvalid,
1367 format!("Invalid date format: {}", s),
1368 path,
1369 locator.get_location(path),
1370 ));
1371 }
1372 }
1373
1374 fn validate_time(
1375 &self,
1376 instance: &Value,
1377 result: &mut ValidationResult,
1378 path: &str,
1379 locator: &JsonSourceLocator,
1380 ) {
1381 let s = match instance {
1382 Value::String(s) => s,
1383 _ => {
1384 result.add_error(ValidationError::instance_error(
1385 InstanceErrorCode::InstanceTimeExpected,
1386 "Expected time string",
1387 path,
1388 locator.get_location(path),
1389 ));
1390 return;
1391 }
1392 };
1393
1394 let valid = NaiveTime::parse_from_str(s, "%H:%M:%S").is_ok()
1396 || NaiveTime::parse_from_str(s, "%H:%M:%S%.f").is_ok()
1397 || NaiveTime::parse_from_str(s, "%H:%M").is_ok();
1398
1399 if !valid {
1400 result.add_error(ValidationError::instance_error(
1401 InstanceErrorCode::InstanceTimeInvalid,
1402 format!("Invalid time format: {}", s),
1403 path,
1404 locator.get_location(path),
1405 ));
1406 }
1407 }
1408
1409 fn validate_datetime(
1410 &self,
1411 instance: &Value,
1412 result: &mut ValidationResult,
1413 path: &str,
1414 locator: &JsonSourceLocator,
1415 ) {
1416 let s = match instance {
1417 Value::String(s) => s,
1418 _ => {
1419 result.add_error(ValidationError::instance_error(
1420 InstanceErrorCode::InstanceDateTimeExpected,
1421 "Expected datetime string",
1422 path,
1423 locator.get_location(path),
1424 ));
1425 return;
1426 }
1427 };
1428
1429 if DateTime::parse_from_rfc3339(s).is_err() {
1431 result.add_error(ValidationError::instance_error(
1432 InstanceErrorCode::InstanceDateTimeInvalid,
1433 format!("Invalid datetime format (expected RFC 3339): {}", s),
1434 path,
1435 locator.get_location(path),
1436 ));
1437 }
1438 }
1439
1440 fn validate_duration(
1441 &self,
1442 instance: &Value,
1443 result: &mut ValidationResult,
1444 path: &str,
1445 locator: &JsonSourceLocator,
1446 ) {
1447 let s = match instance {
1448 Value::String(s) => s,
1449 _ => {
1450 result.add_error(ValidationError::instance_error(
1451 InstanceErrorCode::InstanceDurationExpected,
1452 "Expected duration string",
1453 path,
1454 locator.get_location(path),
1455 ));
1456 return;
1457 }
1458 };
1459
1460 let duration_pattern = Regex::new(r"^P(\d+Y)?(\d+M)?(\d+W)?(\d+D)?(T(\d+H)?(\d+M)?(\d+(\.\d+)?S)?)?$").unwrap();
1462 if !duration_pattern.is_match(s) {
1463 result.add_error(ValidationError::instance_error(
1464 InstanceErrorCode::InstanceDurationInvalid,
1465 format!("Invalid duration format (expected ISO 8601): {}", s),
1466 path,
1467 locator.get_location(path),
1468 ));
1469 }
1470 }
1471
1472 fn validate_uuid(
1475 &self,
1476 instance: &Value,
1477 result: &mut ValidationResult,
1478 path: &str,
1479 locator: &JsonSourceLocator,
1480 ) {
1481 let s = match instance {
1482 Value::String(s) => s,
1483 _ => {
1484 result.add_error(ValidationError::instance_error(
1485 InstanceErrorCode::InstanceUuidExpected,
1486 "Expected UUID string",
1487 path,
1488 locator.get_location(path),
1489 ));
1490 return;
1491 }
1492 };
1493
1494 if Uuid::parse_str(s).is_err() {
1495 result.add_error(ValidationError::instance_error(
1496 InstanceErrorCode::InstanceUuidInvalid,
1497 format!("Invalid UUID format: {}", s),
1498 path,
1499 locator.get_location(path),
1500 ));
1501 }
1502 }
1503
1504 fn validate_uri(
1505 &self,
1506 instance: &Value,
1507 result: &mut ValidationResult,
1508 path: &str,
1509 locator: &JsonSourceLocator,
1510 ) {
1511 let s = match instance {
1512 Value::String(s) => s,
1513 _ => {
1514 result.add_error(ValidationError::instance_error(
1515 InstanceErrorCode::InstanceUriExpected,
1516 "Expected URI string",
1517 path,
1518 locator.get_location(path),
1519 ));
1520 return;
1521 }
1522 };
1523
1524 if url::Url::parse(s).is_err() {
1525 result.add_error(ValidationError::instance_error(
1526 InstanceErrorCode::InstanceUriInvalid,
1527 format!("Invalid URI format: {}", s),
1528 path,
1529 locator.get_location(path),
1530 ));
1531 }
1532 }
1533
1534 fn validate_binary(
1535 &self,
1536 instance: &Value,
1537 result: &mut ValidationResult,
1538 path: &str,
1539 locator: &JsonSourceLocator,
1540 ) {
1541 let s = match instance {
1542 Value::String(s) => s,
1543 _ => {
1544 result.add_error(ValidationError::instance_error(
1545 InstanceErrorCode::InstanceBinaryExpected,
1546 "Expected base64 string",
1547 path,
1548 locator.get_location(path),
1549 ));
1550 return;
1551 }
1552 };
1553
1554 if base64::engine::general_purpose::STANDARD.decode(s).is_err() {
1555 result.add_error(ValidationError::instance_error(
1556 InstanceErrorCode::InstanceBinaryInvalid,
1557 format!("Invalid base64 encoding: {}", s),
1558 path,
1559 locator.get_location(path),
1560 ));
1561 }
1562 }
1563
1564 fn validate_jsonpointer(
1565 &self,
1566 instance: &Value,
1567 result: &mut ValidationResult,
1568 path: &str,
1569 locator: &JsonSourceLocator,
1570 ) {
1571 let s = match instance {
1572 Value::String(s) => s,
1573 _ => {
1574 result.add_error(ValidationError::instance_error(
1575 InstanceErrorCode::InstanceJsonPointerExpected,
1576 "Expected JSON Pointer string",
1577 path,
1578 locator.get_location(path),
1579 ));
1580 return;
1581 }
1582 };
1583
1584 if !s.is_empty() && !s.starts_with('/') {
1586 result.add_error(ValidationError::instance_error(
1587 InstanceErrorCode::InstanceJsonPointerInvalid,
1588 format!("Invalid JSON Pointer format: {}", s),
1589 path,
1590 locator.get_location(path),
1591 ));
1592 }
1593 }
1594
1595 fn validate_object(
1598 &self,
1599 instance: &Value,
1600 schema_obj: &serde_json::Map<String, Value>,
1601 root_schema: &Value,
1602 result: &mut ValidationResult,
1603 path: &str,
1604 locator: &JsonSourceLocator,
1605 depth: usize,
1606 ) {
1607 let obj = match instance {
1608 Value::Object(o) => o,
1609 _ => {
1610 result.add_error(ValidationError::instance_error(
1611 InstanceErrorCode::InstanceObjectExpected,
1612 "Expected object",
1613 path,
1614 locator.get_location(path),
1615 ));
1616 return;
1617 }
1618 };
1619
1620 let properties = schema_obj.get("properties").and_then(Value::as_object);
1621 let required = schema_obj.get("required").and_then(Value::as_array);
1622 let additional_properties = schema_obj.get("additionalProperties");
1623
1624 if let Some(req) = required {
1626 for item in req {
1627 if let Value::String(prop_name) = item {
1628 if !obj.contains_key(prop_name) {
1629 result.add_error(ValidationError::instance_error(
1630 InstanceErrorCode::InstanceRequiredMissing,
1631 format!("Required property missing: {}", prop_name),
1632 path,
1633 locator.get_location(path),
1634 ));
1635 }
1636 }
1637 }
1638 }
1639
1640 for (prop_name, prop_value) in obj {
1642 let prop_path = format!("{}/{}", path, prop_name);
1643
1644 if let Some(props) = properties {
1645 if let Some(prop_schema) = props.get(prop_name) {
1646 self.validate_instance(prop_value, prop_schema, root_schema, result, &prop_path, locator, depth + 1);
1647 } else {
1648 match additional_properties {
1650 Some(Value::Bool(false)) => {
1651 result.add_error(ValidationError::instance_error(
1652 InstanceErrorCode::InstanceAdditionalProperty,
1653 format!("Additional property not allowed: {}", prop_name),
1654 &prop_path,
1655 locator.get_location(&prop_path),
1656 ));
1657 }
1658 Some(Value::Object(_)) => {
1659 self.validate_instance(prop_value, additional_properties.unwrap(), root_schema, result, &prop_path, locator, depth + 1);
1660 }
1661 _ => {}
1662 }
1663 }
1664 }
1665 }
1666
1667 if self.options.extended {
1669 if let Some(Value::Number(n)) = schema_obj.get("minProperties") {
1671 if let Some(min) = n.as_u64() {
1672 if obj.len() < min as usize {
1673 result.add_error(ValidationError::instance_error(
1674 InstanceErrorCode::InstanceTooFewProperties,
1675 format!("Object has {} properties, minimum is {}", obj.len(), min),
1676 path,
1677 locator.get_location(path),
1678 ));
1679 }
1680 }
1681 }
1682
1683 if let Some(Value::Number(n)) = schema_obj.get("maxProperties") {
1685 if let Some(max) = n.as_u64() {
1686 if obj.len() > max as usize {
1687 result.add_error(ValidationError::instance_error(
1688 InstanceErrorCode::InstanceTooManyProperties,
1689 format!("Object has {} properties, maximum is {}", obj.len(), max),
1690 path,
1691 locator.get_location(path),
1692 ));
1693 }
1694 }
1695 }
1696
1697 if let Some(Value::Object(deps)) = schema_obj.get("dependentRequired") {
1699 for (prop, required_props) in deps {
1700 if obj.contains_key(prop) {
1701 if let Value::Array(req) = required_props {
1702 for req_prop in req {
1703 if let Value::String(req_name) = req_prop {
1704 if !obj.contains_key(req_name) {
1705 result.add_error(ValidationError::instance_error(
1706 InstanceErrorCode::InstanceDependentRequiredMissing,
1707 format!("Property '{}' requires '{}' to be present", prop, req_name),
1708 path,
1709 locator.get_location(path),
1710 ));
1711 }
1712 }
1713 }
1714 }
1715 }
1716 }
1717 }
1718
1719 if let Some(Value::Object(pattern_props)) = schema_obj.get("patternProperties") {
1721 for (prop_name, prop_value) in obj {
1722 for (pattern_str, pattern_schema) in pattern_props {
1723 if let Ok(regex) = Regex::new(pattern_str) {
1724 if regex.is_match(prop_name) {
1725 let prop_path = format!("{}/{}", path, prop_name);
1726 self.validate_instance(prop_value, pattern_schema, root_schema, result, &prop_path, locator, depth + 1);
1727 }
1728 }
1729 }
1730 }
1731 }
1732
1733 if let Some(prop_names_schema) = schema_obj.get("propertyNames") {
1735 if let Some(prop_names_obj) = prop_names_schema.as_object() {
1736 if let Some(Value::String(pattern)) = prop_names_obj.get("pattern") {
1738 if let Ok(regex) = Regex::new(pattern) {
1739 for prop_name in obj.keys() {
1740 if !regex.is_match(prop_name) {
1741 result.add_error(ValidationError::instance_error(
1742 InstanceErrorCode::InstancePropertyNameInvalid,
1743 format!("Property name '{}' does not match pattern '{}'", prop_name, pattern),
1744 &format!("{}/{}", path, prop_name),
1745 locator.get_location(&format!("{}/{}", path, prop_name)),
1746 ));
1747 }
1748 }
1749 }
1750 }
1751 if let Some(Value::Number(n)) = prop_names_obj.get("minLength") {
1753 if let Some(min) = n.as_u64() {
1754 for prop_name in obj.keys() {
1755 if prop_name.len() < min as usize {
1756 result.add_error(ValidationError::instance_error(
1757 InstanceErrorCode::InstancePropertyNameInvalid,
1758 format!("Property name '{}' is shorter than minimum length {}", prop_name, min),
1759 &format!("{}/{}", path, prop_name),
1760 locator.get_location(&format!("{}/{}", path, prop_name)),
1761 ));
1762 }
1763 }
1764 }
1765 }
1766 if let Some(Value::Number(n)) = prop_names_obj.get("maxLength") {
1767 if let Some(max) = n.as_u64() {
1768 for prop_name in obj.keys() {
1769 if prop_name.len() > max as usize {
1770 result.add_error(ValidationError::instance_error(
1771 InstanceErrorCode::InstancePropertyNameInvalid,
1772 format!("Property name '{}' is longer than maximum length {}", prop_name, max),
1773 &format!("{}/{}", path, prop_name),
1774 locator.get_location(&format!("{}/{}", path, prop_name)),
1775 ));
1776 }
1777 }
1778 }
1779 }
1780 }
1781 }
1782 }
1783 }
1784
1785 fn validate_array(
1786 &self,
1787 instance: &Value,
1788 schema_obj: &serde_json::Map<String, Value>,
1789 root_schema: &Value,
1790 result: &mut ValidationResult,
1791 path: &str,
1792 locator: &JsonSourceLocator,
1793 depth: usize,
1794 ) {
1795 let arr = match instance {
1796 Value::Array(a) => a,
1797 _ => {
1798 result.add_error(ValidationError::instance_error(
1799 InstanceErrorCode::InstanceArrayExpected,
1800 "Expected array",
1801 path,
1802 locator.get_location(path),
1803 ));
1804 return;
1805 }
1806 };
1807
1808 if let Some(items_schema) = schema_obj.get("items") {
1810 for (i, item) in arr.iter().enumerate() {
1811 let item_path = format!("{}/{}", path, i);
1812 self.validate_instance(item, items_schema, root_schema, result, &item_path, locator, depth + 1);
1813 }
1814 }
1815
1816 if self.options.extended {
1817 if let Some(Value::Number(n)) = schema_obj.get("minItems") {
1819 if let Some(min) = n.as_u64() {
1820 if arr.len() < min as usize {
1821 result.add_error(ValidationError::instance_error(
1822 InstanceErrorCode::InstanceArrayTooShort,
1823 format!("Array length {} is less than minimum {}", arr.len(), min),
1824 path,
1825 locator.get_location(path),
1826 ));
1827 }
1828 }
1829 }
1830
1831 if let Some(Value::Number(n)) = schema_obj.get("maxItems") {
1833 if let Some(max) = n.as_u64() {
1834 if arr.len() > max as usize {
1835 result.add_error(ValidationError::instance_error(
1836 InstanceErrorCode::InstanceArrayTooLong,
1837 format!("Array length {} is greater than maximum {}", arr.len(), max),
1838 path,
1839 locator.get_location(path),
1840 ));
1841 }
1842 }
1843 }
1844
1845 if let Some(contains_schema) = schema_obj.get("contains") {
1847 let mut match_count = 0;
1848 for item in arr.iter() {
1849 let mut temp_result = ValidationResult::new();
1850 self.validate_instance(item, contains_schema, root_schema, &mut temp_result, path, locator, depth + 1);
1851 if temp_result.is_valid() {
1852 match_count += 1;
1853 }
1854 }
1855
1856 let min_contains = schema_obj.get("minContains")
1857 .and_then(Value::as_u64)
1858 .unwrap_or(1);
1859 let max_contains = schema_obj.get("maxContains")
1860 .and_then(Value::as_u64);
1861
1862 if match_count < min_contains as usize {
1863 result.add_error(ValidationError::instance_error(
1864 InstanceErrorCode::InstanceArrayContainsTooFew,
1865 format!("Array contains {} matching items, minimum is {}", match_count, min_contains),
1866 path,
1867 locator.get_location(path),
1868 ));
1869 }
1870
1871 if let Some(max) = max_contains {
1872 if match_count > max as usize {
1873 result.add_error(ValidationError::instance_error(
1874 InstanceErrorCode::InstanceArrayContainsTooMany,
1875 format!("Array contains {} matching items, maximum is {}", match_count, max),
1876 path,
1877 locator.get_location(path),
1878 ));
1879 }
1880 }
1881 }
1882
1883 if let Some(Value::Bool(true)) = schema_obj.get("uniqueItems") {
1885 let mut seen = Vec::new();
1886 for (i, item) in arr.iter().enumerate() {
1887 let item_str = item.to_string();
1888 if seen.contains(&item_str) {
1889 result.add_error(ValidationError::instance_error(
1890 InstanceErrorCode::InstanceArrayNotUnique,
1891 format!("Array items are not unique (duplicate at index {})", i),
1892 path,
1893 locator.get_location(path),
1894 ));
1895 break;
1896 }
1897 seen.push(item_str);
1898 }
1899 }
1900 }
1901 }
1902
1903 fn validate_set(
1904 &self,
1905 instance: &Value,
1906 schema_obj: &serde_json::Map<String, Value>,
1907 root_schema: &Value,
1908 result: &mut ValidationResult,
1909 path: &str,
1910 locator: &JsonSourceLocator,
1911 depth: usize,
1912 ) {
1913 let arr = match instance {
1914 Value::Array(a) => a,
1915 _ => {
1916 result.add_error(ValidationError::instance_error(
1917 InstanceErrorCode::InstanceSetExpected,
1918 "Expected array (set)",
1919 path,
1920 locator.get_location(path),
1921 ));
1922 return;
1923 }
1924 };
1925
1926 let mut seen = Vec::new();
1928 for (i, item) in arr.iter().enumerate() {
1929 let item_str = item.to_string();
1930 if seen.contains(&item_str) {
1931 result.add_error(ValidationError::instance_error(
1932 InstanceErrorCode::InstanceSetNotUnique,
1933 "Set contains duplicate values",
1934 &format!("{}/{}", path, i),
1935 locator.get_location(&format!("{}/{}", path, i)),
1936 ));
1937 } else {
1938 seen.push(item_str);
1939 }
1940 }
1941
1942 if let Some(items_schema) = schema_obj.get("items") {
1944 for (i, item) in arr.iter().enumerate() {
1945 let item_path = format!("{}/{}", path, i);
1946 self.validate_instance(item, items_schema, root_schema, result, &item_path, locator, depth + 1);
1947 }
1948 }
1949 }
1950
1951 fn validate_map(
1952 &self,
1953 instance: &Value,
1954 schema_obj: &serde_json::Map<String, Value>,
1955 root_schema: &Value,
1956 result: &mut ValidationResult,
1957 path: &str,
1958 locator: &JsonSourceLocator,
1959 depth: usize,
1960 ) {
1961 let obj = match instance {
1962 Value::Object(o) => o,
1963 _ => {
1964 result.add_error(ValidationError::instance_error(
1965 InstanceErrorCode::InstanceMapExpected,
1966 "Expected object (map)",
1967 path,
1968 locator.get_location(path),
1969 ));
1970 return;
1971 }
1972 };
1973
1974 if let Some(values_schema) = schema_obj.get("values") {
1976 for (key, value) in obj.iter() {
1977 let value_path = format!("{}/{}", path, key);
1978 self.validate_instance(value, values_schema, root_schema, result, &value_path, locator, depth + 1);
1979 }
1980 }
1981
1982 if self.options.extended {
1984 if let Some(Value::Number(n)) = schema_obj.get("minEntries") {
1986 if let Some(min) = n.as_u64() {
1987 if obj.len() < min as usize {
1988 result.add_error(ValidationError::instance_error(
1989 InstanceErrorCode::InstanceMapTooFewEntries,
1990 format!("Map has {} entries, minimum is {}", obj.len(), min),
1991 path,
1992 locator.get_location(path),
1993 ));
1994 }
1995 }
1996 }
1997
1998 if let Some(Value::Number(n)) = schema_obj.get("maxEntries") {
2000 if let Some(max) = n.as_u64() {
2001 if obj.len() > max as usize {
2002 result.add_error(ValidationError::instance_error(
2003 InstanceErrorCode::InstanceMapTooManyEntries,
2004 format!("Map has {} entries, maximum is {}", obj.len(), max),
2005 path,
2006 locator.get_location(path),
2007 ));
2008 }
2009 }
2010 }
2011
2012 if let Some(keynames_schema) = schema_obj.get("keyNames") {
2014 if let Some(keynames_obj) = keynames_schema.as_object() {
2015 if let Some(Value::String(pattern)) = keynames_obj.get("pattern") {
2016 if let Ok(re) = Regex::new(pattern) {
2017 for key in obj.keys() {
2018 if !re.is_match(key) {
2019 result.add_error(ValidationError::instance_error(
2020 InstanceErrorCode::InstanceMapKeyPatternMismatch,
2021 format!("Map key '{}' does not match pattern '{}'", key, pattern),
2022 path,
2023 locator.get_location(path),
2024 ));
2025 }
2026 }
2027 }
2028 }
2029 if let Some(Value::Number(n)) = keynames_obj.get("minLength") {
2031 if let Some(min) = n.as_u64() {
2032 for key in obj.keys() {
2033 if key.len() < min as usize {
2034 result.add_error(ValidationError::instance_error(
2035 InstanceErrorCode::InstanceMapKeyPatternMismatch,
2036 format!("Map key '{}' is shorter than minimum length {}", key, min),
2037 path,
2038 locator.get_location(path),
2039 ));
2040 }
2041 }
2042 }
2043 }
2044 if let Some(Value::Number(n)) = keynames_obj.get("maxLength") {
2045 if let Some(max) = n.as_u64() {
2046 for key in obj.keys() {
2047 if key.len() > max as usize {
2048 result.add_error(ValidationError::instance_error(
2049 InstanceErrorCode::InstanceMapKeyPatternMismatch,
2050 format!("Map key '{}' is longer than maximum length {}", key, max),
2051 path,
2052 locator.get_location(path),
2053 ));
2054 }
2055 }
2056 }
2057 }
2058 }
2059 }
2060
2061 if let Some(Value::Object(pattern_keys)) = schema_obj.get("patternKeys") {
2063 for (key, value) in obj {
2064 for (pattern_str, pattern_schema) in pattern_keys {
2065 if let Ok(regex) = Regex::new(pattern_str) {
2066 if regex.is_match(key) {
2067 let value_path = format!("{}/{}", path, key);
2068 self.validate_instance(value, pattern_schema, root_schema, result, &value_path, locator, depth + 1);
2069 }
2070 }
2071 }
2072 }
2073 }
2074 }
2075 }
2076
2077 fn validate_tuple(
2078 &self,
2079 instance: &Value,
2080 schema_obj: &serde_json::Map<String, Value>,
2081 root_schema: &Value,
2082 result: &mut ValidationResult,
2083 path: &str,
2084 locator: &JsonSourceLocator,
2085 depth: usize,
2086 ) {
2087 let arr = match instance {
2088 Value::Array(a) => a,
2089 _ => {
2090 result.add_error(ValidationError::instance_error(
2091 InstanceErrorCode::InstanceTupleExpected,
2092 "Expected array (tuple)",
2093 path,
2094 locator.get_location(path),
2095 ));
2096 return;
2097 }
2098 };
2099
2100 let properties = schema_obj.get("properties").and_then(Value::as_object);
2101 let tuple_order = schema_obj.get("tuple").and_then(Value::as_array);
2102
2103 if let (Some(props), Some(order)) = (properties, tuple_order) {
2104 if arr.len() != order.len() {
2106 result.add_error(ValidationError::instance_error(
2107 InstanceErrorCode::InstanceTupleLengthMismatch,
2108 format!("Tuple length {} does not match expected {}", arr.len(), order.len()),
2109 path,
2110 locator.get_location(path),
2111 ));
2112 return;
2113 }
2114
2115 for (i, prop_name_val) in order.iter().enumerate() {
2117 if let Value::String(prop_name) = prop_name_val {
2118 if let Some(prop_schema) = props.get(prop_name) {
2119 let elem_path = format!("{}/{}", path, i);
2120 self.validate_instance(&arr[i], prop_schema, root_schema, result, &elem_path, locator, depth + 1);
2121 }
2122 }
2123 }
2124 }
2125 }
2126
2127 fn validate_choice(
2128 &self,
2129 instance: &Value,
2130 schema_obj: &serde_json::Map<String, Value>,
2131 root_schema: &Value,
2132 result: &mut ValidationResult,
2133 path: &str,
2134 locator: &JsonSourceLocator,
2135 depth: usize,
2136 ) {
2137 let choices = match schema_obj.get("choices").and_then(Value::as_object) {
2138 Some(c) => c,
2139 None => return,
2140 };
2141
2142 let selector = schema_obj.get("selector").and_then(Value::as_str);
2143
2144 if let Some(selector_prop) = selector {
2145 let obj = match instance {
2147 Value::Object(o) => o,
2148 _ => {
2149 result.add_error(ValidationError::instance_error(
2150 InstanceErrorCode::InstanceObjectExpected,
2151 "Choice with selector expects object",
2152 path,
2153 locator.get_location(path),
2154 ));
2155 return;
2156 }
2157 };
2158
2159 let selector_value = match obj.get(selector_prop) {
2160 Some(Value::String(s)) => s.as_str(),
2161 Some(_) => {
2162 result.add_error(ValidationError::instance_error(
2163 InstanceErrorCode::InstanceChoiceSelectorInvalid,
2164 format!("Selector property '{}' must be a string", selector_prop),
2165 &format!("{}/{}", path, selector_prop),
2166 locator.get_location(&format!("{}/{}", path, selector_prop)),
2167 ));
2168 return;
2169 }
2170 None => {
2171 result.add_error(ValidationError::instance_error(
2172 InstanceErrorCode::InstanceChoiceSelectorMissing,
2173 format!("Missing selector property: {}", selector_prop),
2174 path,
2175 locator.get_location(path),
2176 ));
2177 return;
2178 }
2179 };
2180
2181 if let Some(choice_schema) = choices.get(selector_value) {
2182 self.validate_instance(instance, choice_schema, root_schema, result, path, locator, depth + 1);
2183 } else {
2184 result.add_error(ValidationError::instance_error(
2185 InstanceErrorCode::InstanceChoiceUnknown,
2186 format!("Unknown choice: {}", selector_value),
2187 path,
2188 locator.get_location(path),
2189 ));
2190 }
2191 } else {
2192 let obj = match instance {
2195 Value::Object(o) => o,
2196 _ => {
2197 result.add_error(ValidationError::instance_error(
2198 InstanceErrorCode::InstanceChoiceNoMatch,
2199 "Value does not match any choice option",
2200 path,
2201 locator.get_location(path),
2202 ));
2203 return;
2204 }
2205 };
2206
2207 if obj.len() == 1 {
2209 let (tag, value) = obj.iter().next().unwrap();
2210 if let Some(choice_schema) = choices.get(tag) {
2211 let value_path = format!("{}/{}", path, tag);
2213 self.validate_instance(value, choice_schema, root_schema, result, &value_path, locator, depth + 1);
2214 return;
2215 }
2216 }
2217
2218 let mut match_count = 0;
2220
2221 for (_choice_name, choice_schema) in choices {
2222 let mut choice_result = ValidationResult::new();
2223 self.validate_instance(instance, choice_schema, root_schema, &mut choice_result, path, locator, depth + 1);
2224 if choice_result.is_valid() {
2225 match_count += 1;
2226 }
2227 }
2228
2229 if match_count == 0 {
2230 result.add_error(ValidationError::instance_error(
2231 InstanceErrorCode::InstanceChoiceNoMatch,
2232 "Value does not match any choice option",
2233 path,
2234 locator.get_location(path),
2235 ));
2236 } else if match_count > 1 {
2237 result.add_error(ValidationError::instance_error(
2238 InstanceErrorCode::InstanceChoiceMultipleMatches,
2239 format!("Value matches {} choice options (should match exactly one)", match_count),
2240 path,
2241 locator.get_location(path),
2242 ));
2243 }
2244 }
2245 }
2246
2247 fn validate_composition(
2250 &self,
2251 instance: &Value,
2252 schema_obj: &serde_json::Map<String, Value>,
2253 root_schema: &Value,
2254 result: &mut ValidationResult,
2255 path: &str,
2256 locator: &JsonSourceLocator,
2257 depth: usize,
2258 ) {
2259 if let Some(Value::Array(schemas)) = schema_obj.get("allOf") {
2261 for schema in schemas {
2262 let mut sub_result = ValidationResult::new();
2263 self.validate_instance(instance, schema, root_schema, &mut sub_result, path, locator, depth + 1);
2264 if !sub_result.is_valid() {
2265 result.add_error(ValidationError::instance_error(
2266 InstanceErrorCode::InstanceAllOfFailed,
2267 "Value does not match all schemas in allOf",
2268 path,
2269 locator.get_location(path),
2270 ));
2271 result.add_errors(sub_result.all_errors().iter().cloned());
2272 return;
2273 }
2274 }
2275 }
2276
2277 if let Some(Value::Array(schemas)) = schema_obj.get("anyOf") {
2279 let mut any_valid = false;
2280 for schema in schemas {
2281 let mut sub_result = ValidationResult::new();
2282 self.validate_instance(instance, schema, root_schema, &mut sub_result, path, locator, depth + 1);
2283 if sub_result.is_valid() {
2284 any_valid = true;
2285 break;
2286 }
2287 }
2288 if !any_valid {
2289 result.add_error(ValidationError::instance_error(
2290 InstanceErrorCode::InstanceAnyOfFailed,
2291 "Value does not match any schema in anyOf",
2292 path,
2293 locator.get_location(path),
2294 ));
2295 }
2296 }
2297
2298 if let Some(Value::Array(schemas)) = schema_obj.get("oneOf") {
2300 let mut match_count = 0;
2301 for schema in schemas {
2302 let mut sub_result = ValidationResult::new();
2303 self.validate_instance(instance, schema, root_schema, &mut sub_result, path, locator, depth + 1);
2304 if sub_result.is_valid() {
2305 match_count += 1;
2306 }
2307 }
2308 if match_count == 0 {
2309 result.add_error(ValidationError::instance_error(
2310 InstanceErrorCode::InstanceOneOfFailed,
2311 "Value does not match any schema in oneOf",
2312 path,
2313 locator.get_location(path),
2314 ));
2315 } else if match_count > 1 {
2316 result.add_error(ValidationError::instance_error(
2317 InstanceErrorCode::InstanceOneOfMultiple,
2318 format!("Value matches {} schemas in oneOf (should match exactly one)", match_count),
2319 path,
2320 locator.get_location(path),
2321 ));
2322 }
2323 }
2324
2325 if let Some(not_schema) = schema_obj.get("not") {
2327 let mut sub_result = ValidationResult::new();
2328 self.validate_instance(instance, not_schema, root_schema, &mut sub_result, path, locator, depth + 1);
2329 if sub_result.is_valid() {
2330 result.add_error(ValidationError::instance_error(
2331 InstanceErrorCode::InstanceNotFailed,
2332 "Value should not match the schema in 'not'",
2333 path,
2334 locator.get_location(path),
2335 ));
2336 }
2337 }
2338
2339 if let Some(if_schema) = schema_obj.get("if") {
2341 let mut if_result = ValidationResult::new();
2342 self.validate_instance(instance, if_schema, root_schema, &mut if_result, path, locator, depth + 1);
2343
2344 if if_result.is_valid() {
2345 if let Some(then_schema) = schema_obj.get("then") {
2346 self.validate_instance(instance, then_schema, root_schema, result, path, locator, depth + 1);
2347 }
2348 } else if let Some(else_schema) = schema_obj.get("else") {
2349 self.validate_instance(instance, else_schema, root_schema, result, path, locator, depth + 1);
2350 }
2351 }
2352 }
2353}
2354
2355#[cfg(test)]
2356mod tests {
2357 use super::*;
2358
2359 fn make_schema(type_name: &str) -> Value {
2360 serde_json::json!({
2361 "$id": "https://example.com/test",
2362 "name": "Test",
2363 "type": type_name
2364 })
2365 }
2366
2367 #[test]
2368 fn test_string_valid() {
2369 let validator = InstanceValidator::new();
2370 let schema = make_schema("string");
2371 let result = validator.validate(r#""hello""#, &schema);
2372 assert!(result.is_valid());
2373 }
2374
2375 #[test]
2376 fn test_string_invalid() {
2377 let validator = InstanceValidator::new();
2378 let schema = make_schema("string");
2379 let result = validator.validate("123", &schema);
2380 assert!(!result.is_valid());
2381 }
2382
2383 #[test]
2384 fn test_boolean_valid() {
2385 let validator = InstanceValidator::new();
2386 let schema = make_schema("boolean");
2387 let result = validator.validate("true", &schema);
2388 assert!(result.is_valid());
2389 }
2390
2391 #[test]
2392 fn test_int32_valid() {
2393 let validator = InstanceValidator::new();
2394 let schema = make_schema("int32");
2395 let result = validator.validate("42", &schema);
2396 assert!(result.is_valid());
2397 }
2398
2399 #[test]
2400 fn test_object_valid() {
2401 let validator = InstanceValidator::new();
2402 let schema = serde_json::json!({
2403 "$id": "https://example.com/test",
2404 "name": "Test",
2405 "type": "object",
2406 "properties": {
2407 "name": { "type": "string" }
2408 },
2409 "required": ["name"]
2410 });
2411 let result = validator.validate(r#"{"name": "test"}"#, &schema);
2412 assert!(result.is_valid());
2413 }
2414
2415 #[test]
2416 fn test_object_missing_required() {
2417 let validator = InstanceValidator::new();
2418 let schema = serde_json::json!({
2419 "$id": "https://example.com/test",
2420 "name": "Test",
2421 "type": "object",
2422 "properties": {
2423 "name": { "type": "string" }
2424 },
2425 "required": ["name"]
2426 });
2427 let result = validator.validate(r#"{}"#, &schema);
2428 assert!(!result.is_valid());
2429 }
2430
2431 #[test]
2432 fn test_array_valid() {
2433 let validator = InstanceValidator::new();
2434 let schema = serde_json::json!({
2435 "$id": "https://example.com/test",
2436 "name": "Test",
2437 "type": "array",
2438 "items": { "type": "int32" }
2439 });
2440 let result = validator.validate("[1, 2, 3]", &schema);
2441 assert!(result.is_valid());
2442 }
2443
2444 #[test]
2445 fn test_enum_valid() {
2446 let validator = InstanceValidator::new();
2447 let schema = serde_json::json!({
2448 "$id": "https://example.com/test",
2449 "name": "Test",
2450 "type": "string",
2451 "enum": ["a", "b", "c"]
2452 });
2453 let result = validator.validate(r#""b""#, &schema);
2454 assert!(result.is_valid());
2455 }
2456
2457 #[test]
2458 fn test_enum_invalid() {
2459 let validator = InstanceValidator::new();
2460 let schema = serde_json::json!({
2461 "$id": "https://example.com/test",
2462 "name": "Test",
2463 "type": "string",
2464 "enum": ["a", "b", "c"]
2465 });
2466 let result = validator.validate(r#""d""#, &schema);
2467 assert!(!result.is_valid());
2468 }
2469}