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