json_structure/
instance_validator.rs

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