Skip to main content

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