Skip to main content

json_structure/
schema_validator.rs

1//! Schema validator for JSON Structure schemas.
2//!
3//! Validates that JSON Structure schema documents are syntactically and semantically correct.
4
5use std::collections::{HashMap, HashSet};
6
7use serde_json::Value;
8
9use crate::error_codes::SchemaErrorCode;
10use crate::json_source_locator::JsonSourceLocator;
11use crate::types::{
12    is_valid_type, JsonLocation, SchemaValidatorOptions, ValidationError, ValidationResult,
13    COMPOSITION_KEYWORDS, KNOWN_EXTENSIONS, VALIDATION_EXTENSION_KEYWORDS,
14};
15
16/// Validates JSON Structure schema documents.
17///
18/// # Example
19///
20/// ```
21/// use json_structure::SchemaValidator;
22///
23/// let validator = SchemaValidator::new();
24/// let result = validator.validate(r#"{"$id": "test", "name": "Test", "type": "string"}"#);
25/// assert!(result.is_valid());
26/// ```
27pub struct SchemaValidator {
28    options: SchemaValidatorOptions,
29    #[allow(dead_code)]
30    external_schemas: HashMap<String, Value>,
31}
32
33impl Default for SchemaValidator {
34    fn default() -> Self {
35        Self::new()
36    }
37}
38
39impl SchemaValidator {
40    /// Creates a new schema validator with default options.
41    #[must_use]
42    pub fn new() -> Self {
43        Self::with_options(SchemaValidatorOptions::default())
44    }
45
46    /// Creates a new schema validator with the given options.
47    #[must_use]
48    pub fn with_options(options: SchemaValidatorOptions) -> Self {
49        let mut external_schemas = HashMap::new();
50        for schema in &options.external_schemas {
51            if let Some(id) = schema.get("$id").and_then(Value::as_str) {
52                external_schemas.insert(id.to_string(), schema.clone());
53            }
54        }
55        Self {
56            options,
57            external_schemas,
58        }
59    }
60
61    /// Enables or disables extended validation mode.
62    /// Note: Schema validation always validates extended keywords; this is a placeholder for API consistency.
63    pub fn set_extended(&mut self, _extended: bool) {
64        // Schema validation always includes extended keyword validation
65    }
66
67    /// Returns whether extended validation is enabled.
68    /// Note: Schema validation always validates extended keywords.
69    pub fn is_extended(&self) -> bool {
70        true // Schema validation always includes extended keyword validation
71    }
72
73    /// Enables or disables warnings for extension keywords without $uses.
74    pub fn set_warn_on_extension_keywords(&mut self, warn: bool) {
75        self.options.warn_on_unused_extension_keywords = warn;
76    }
77
78    /// Returns whether warnings are enabled for extension keywords.
79    pub fn is_warn_on_extension_keywords(&self) -> bool {
80        self.options.warn_on_unused_extension_keywords
81    }
82
83    /// Validates a JSON Structure schema from a string.
84    ///
85    /// Returns a [`ValidationResult`] that should be checked with [`is_valid()`](ValidationResult::is_valid).
86    #[must_use]
87    pub fn validate(&self, schema_json: &str) -> ValidationResult {
88        let mut result = ValidationResult::new();
89        let locator = JsonSourceLocator::new(schema_json);
90
91        match serde_json::from_str::<Value>(schema_json) {
92            Ok(schema) => {
93                self.validate_schema_internal(&schema, &schema, &locator, &mut result, "", true, &mut HashSet::new(), 0);
94            }
95            Err(e) => {
96                result.add_error(ValidationError::schema_error(
97                    SchemaErrorCode::SchemaInvalidType,
98                    format!("Failed to parse JSON: {}", e),
99                    "",
100                    JsonLocation::unknown(),
101                ));
102            }
103        }
104
105        result
106    }
107
108    /// Validates a JSON Structure schema from a parsed Value.
109    ///
110    /// Returns a [`ValidationResult`] that should be checked with [`is_valid()`](ValidationResult::is_valid).
111    #[must_use]
112    pub fn validate_value(&self, schema: &Value, schema_json: &str) -> ValidationResult {
113        let mut result = ValidationResult::new();
114        let locator = JsonSourceLocator::new(schema_json);
115        self.validate_schema_internal(schema, schema, &locator, &mut result, "", true, &mut HashSet::new(), 0);
116        result
117    }
118
119    /// Validates a schema node (internal helper that uses a separate root_schema).
120    fn validate_schema(
121        &self,
122        schema: &Value,
123        root_schema: &Value,
124        locator: &JsonSourceLocator,
125        result: &mut ValidationResult,
126        path: &str,
127        is_root: bool,
128        visited_refs: &mut HashSet<String>,
129        depth: usize,
130    ) {
131        // Pass through to internal method with proper root_schema
132        self.validate_schema_internal(schema, root_schema, locator, result, path, is_root, visited_refs, depth);
133    }
134
135    /// Internal schema validation with root schema reference.
136    fn validate_schema_internal(
137        &self,
138        schema: &Value,
139        root_schema: &Value,
140        locator: &JsonSourceLocator,
141        result: &mut ValidationResult,
142        path: &str,
143        is_root: bool,
144        visited_refs: &mut HashSet<String>,
145        depth: usize,
146    ) {
147        if depth > self.options.max_validation_depth {
148            return;
149        }
150
151        // Schema must be an object or boolean
152        match schema {
153            Value::Null => {
154                result.add_error(ValidationError::schema_error(
155                    SchemaErrorCode::SchemaNull,
156                    "Schema cannot be null",
157                    path,
158                    locator.get_location(path),
159                ));
160                return;
161            }
162            Value::Bool(_) => {
163                // Boolean schemas are valid
164                return;
165            }
166            Value::Object(_obj) => {
167                // Continue validation
168            }
169            _ => {
170                result.add_error(ValidationError::schema_error(
171                    SchemaErrorCode::SchemaInvalidType,
172                    "Schema must be an object or boolean",
173                    path,
174                    locator.get_location(path),
175                ));
176                return;
177            }
178        }
179
180        let obj = schema.as_object().unwrap();
181
182        // Collect enabled extensions from root schema (extensions are inherited)
183        let mut enabled_extensions = HashSet::new();
184        if let Some(root_obj) = root_schema.as_object() {
185            if let Some(Value::Array(arr)) = root_obj.get("$uses") {
186                for ext in arr {
187                    if let Value::String(s) = ext {
188                        enabled_extensions.insert(s.as_str());
189                    }
190                }
191            }
192        }
193
194        // Root schema validation
195        if is_root {
196            self.validate_root_schema(obj, locator, result, path);
197        }
198
199        // Validate $ref if present
200        if let Some(ref_val) = obj.get("$ref") {
201            self.validate_ref(ref_val, schema, root_schema, locator, result, path, visited_refs, depth);
202        }
203
204        // Validate type if present
205        if let Some(type_val) = obj.get("type") {
206            self.validate_type(type_val, obj, root_schema, locator, result, path, &enabled_extensions, visited_refs, depth);
207        }
208
209        // Validate $extends if present
210        if let Some(extends_val) = obj.get("$extends") {
211            self.validate_extends(extends_val, root_schema, locator, result, path, visited_refs, depth);
212        }
213
214        // Validate altnames if present
215        if let Some(altnames_val) = obj.get("altnames") {
216            self.validate_altnames(altnames_val, locator, result, path);
217        }
218
219        // Validate definitions
220        if let Some(defs) = obj.get("definitions") {
221            self.validate_definitions(defs, root_schema, locator, result, path, visited_refs, depth);
222        }
223
224        // Validate enum
225        if let Some(enum_val) = obj.get("enum") {
226            self.validate_enum(enum_val, locator, result, path);
227        }
228
229        // Validate composition keywords
230        self.validate_composition(obj, root_schema, locator, result, path, &enabled_extensions, visited_refs, depth);
231
232        // Validate extension keywords without $uses
233        if self.options.warn_on_unused_extension_keywords {
234            self.check_extension_keywords(obj, locator, result, path, &enabled_extensions);
235        }
236    }
237
238    /// Validates root schema requirements.
239    fn validate_root_schema(
240        &self,
241        obj: &serde_json::Map<String, Value>,
242        locator: &JsonSourceLocator,
243        result: &mut ValidationResult,
244        path: &str,
245    ) {
246        // Root must have $id
247        if !obj.contains_key("$id") {
248            result.add_error(ValidationError::schema_error(
249                SchemaErrorCode::SchemaRootMissingId,
250                "Root schema must have $id",
251                path,
252                locator.get_location(path),
253            ));
254        } else if let Some(id) = obj.get("$id") {
255            if !id.is_string() {
256                result.add_error(ValidationError::schema_error(
257                    SchemaErrorCode::SchemaRootMissingId,
258                    "$id must be a string",
259                    &format!("{}/$id", path),
260                    locator.get_location(&format!("{}/$id", path)),
261                ));
262            }
263        }
264
265        // If type is present, name is required
266        if obj.contains_key("type") && !obj.contains_key("name") {
267            result.add_error(ValidationError::schema_error(
268                SchemaErrorCode::SchemaRootMissingName,
269                "Root schema with type must have name",
270                path,
271                locator.get_location(path),
272            ));
273        }
274
275        // Root must have type OR $root OR definitions OR composition keyword
276        let has_type = obj.contains_key("type");
277        let has_root = obj.contains_key("$root");
278        let has_definitions = obj.contains_key("definitions");
279        let has_composition = obj.keys().any(|k| 
280            ["allOf", "anyOf", "oneOf", "not", "if"].contains(&k.as_str())
281        );
282        
283        if !has_type && !has_root && !has_composition {
284            // Check if it has only meta keywords + definitions
285            let has_only_meta = obj.keys().all(|k| {
286                k.starts_with('$') || k == "definitions" || k == "name" || k == "description"
287            });
288            
289            if !has_only_meta || !has_definitions {
290                result.add_error(ValidationError::schema_error(
291                    SchemaErrorCode::SchemaRootMissingType,
292                    "Schema must have a 'type' property or '$root' reference",
293                    path,
294                    locator.get_location(path),
295                ));
296            }
297        }
298
299        // Validate $uses
300        if let Some(uses) = obj.get("$uses") {
301            self.validate_uses(uses, locator, result, path);
302        }
303
304        // Validate $offers
305        if let Some(offers) = obj.get("$offers") {
306            self.validate_offers(offers, locator, result, path);
307        }
308    }
309
310    /// Validates $uses keyword.
311    fn validate_uses(
312        &self,
313        uses: &Value,
314        locator: &JsonSourceLocator,
315        result: &mut ValidationResult,
316        path: &str,
317    ) {
318        let uses_path = format!("{}/$uses", path);
319        match uses {
320            Value::Array(arr) => {
321                for (i, ext) in arr.iter().enumerate() {
322                    if let Value::String(s) = ext {
323                        if !KNOWN_EXTENSIONS.contains(&s.as_str()) {
324                            result.add_error(ValidationError::schema_warning(
325                                SchemaErrorCode::SchemaUsesInvalidExtension,
326                                format!("Unknown extension: {}", s),
327                                &format!("{}/{}", uses_path, i),
328                                locator.get_location(&format!("{}/{}", uses_path, i)),
329                            ));
330                        }
331                    } else {
332                        result.add_error(ValidationError::schema_error(
333                            SchemaErrorCode::SchemaUsesInvalidExtension,
334                            "Extension name must be a string",
335                            &format!("{}/{}", uses_path, i),
336                            locator.get_location(&format!("{}/{}", uses_path, i)),
337                        ));
338                    }
339                }
340            }
341            _ => {
342                result.add_error(ValidationError::schema_error(
343                    SchemaErrorCode::SchemaUsesNotArray,
344                    "$uses must be an array",
345                    &uses_path,
346                    locator.get_location(&uses_path),
347                ));
348            }
349        }
350    }
351
352    /// Validates $offers keyword.
353    fn validate_offers(
354        &self,
355        offers: &Value,
356        locator: &JsonSourceLocator,
357        result: &mut ValidationResult,
358        path: &str,
359    ) {
360        let offers_path = format!("{}/$offers", path);
361        match offers {
362            Value::Array(arr) => {
363                for (i, ext) in arr.iter().enumerate() {
364                    if !ext.is_string() {
365                        result.add_error(ValidationError::schema_error(
366                            SchemaErrorCode::SchemaOffersInvalidExtension,
367                            "Extension name must be a string",
368                            &format!("{}/{}", offers_path, i),
369                            locator.get_location(&format!("{}/{}", offers_path, i)),
370                        ));
371                    }
372                }
373            }
374            _ => {
375                result.add_error(ValidationError::schema_error(
376                    SchemaErrorCode::SchemaOffersNotArray,
377                    "$offers must be an array",
378                    &offers_path,
379                    locator.get_location(&offers_path),
380                ));
381            }
382        }
383    }
384
385    /// Validates a $ref reference.
386    fn validate_ref(
387        &self,
388        ref_val: &Value,
389        _schema: &Value,
390        root_schema: &Value,
391        locator: &JsonSourceLocator,
392        result: &mut ValidationResult,
393        path: &str,
394        visited_refs: &mut HashSet<String>,
395        _depth: usize,
396    ) {
397        let ref_path = format!("{}/$ref", path);
398
399        match ref_val {
400            Value::String(ref_str) => {
401                // Check for circular reference
402                if visited_refs.contains(ref_str) {
403                    result.add_error(ValidationError::schema_error(
404                        SchemaErrorCode::SchemaRefCircular,
405                        format!("Circular reference detected: {}", ref_str),
406                        &ref_path,
407                        locator.get_location(&ref_path),
408                    ));
409                    return;
410                }
411
412                // Validate reference format
413                if ref_str.starts_with("#/definitions/") {
414                    // Local definition reference - resolve from root schema
415                    if let Some(resolved) = self.resolve_ref(ref_str, root_schema) {
416                        // Check for direct circular reference (definition is only a $ref to itself)
417                        if let Value::Object(def_obj) = resolved {
418                            let keys: Vec<&String> = def_obj.keys().collect();
419                            let is_bare_ref = keys.len() == 1 && keys[0] == "$ref";
420                            let is_type_ref_only = keys.len() == 1 && keys[0] == "type" && {
421                                if let Some(Value::Object(type_obj)) = def_obj.get("type") {
422                                    type_obj.len() == 1 && type_obj.contains_key("$ref")
423                                } else {
424                                    false
425                                }
426                            };
427                            
428                            if is_bare_ref || is_type_ref_only {
429                                // Check if it references itself
430                                let inner_ref = if is_bare_ref {
431                                    def_obj.get("$ref").and_then(|v| v.as_str())
432                                } else if is_type_ref_only {
433                                    def_obj.get("type")
434                                        .and_then(|t| t.as_object())
435                                        .and_then(|o| o.get("$ref"))
436                                        .and_then(|v| v.as_str())
437                                } else {
438                                    None
439                                };
440                                
441                                if inner_ref == Some(ref_str) {
442                                    result.add_error(ValidationError::schema_error(
443                                        SchemaErrorCode::SchemaRefCircular,
444                                        format!("Direct circular reference: {}", ref_str),
445                                        &ref_path,
446                                        locator.get_location(&ref_path),
447                                    ));
448                                }
449                            }
450                        }
451                    } else {
452                        result.add_error(ValidationError::schema_error(
453                            SchemaErrorCode::SchemaRefNotFound,
454                            format!("Reference not found: {}", ref_str),
455                            &ref_path,
456                            locator.get_location(&ref_path),
457                        ));
458                    }
459                }
460                // External references would be validated here if import is enabled
461            }
462            _ => {
463                result.add_error(ValidationError::schema_error(
464                    SchemaErrorCode::SchemaRefNotString,
465                    "$ref must be a string",
466                    &ref_path,
467                    locator.get_location(&ref_path),
468                ));
469            }
470        }
471    }
472
473    /// Resolves a $ref reference to its target definition.
474    fn resolve_ref<'a>(&self, ref_str: &str, root_schema: &'a Value) -> Option<&'a Value> {
475        if !ref_str.starts_with("#/") {
476            return None;
477        }
478        
479        let path_parts: Vec<&str> = ref_str[2..].split('/').collect();
480        let mut current = root_schema;
481        
482        for part in path_parts {
483            // Handle JSON Pointer escaping
484            let unescaped = part.replace("~1", "/").replace("~0", "~");
485            current = current.get(&unescaped)?;
486        }
487        
488        Some(current)
489    }
490
491    /// Validates the type keyword and type-specific requirements.
492    fn validate_type(
493        &self,
494        type_val: &Value,
495        obj: &serde_json::Map<String, Value>,
496        root_schema: &Value,
497        locator: &JsonSourceLocator,
498        result: &mut ValidationResult,
499        path: &str,
500        enabled_extensions: &HashSet<&str>,
501        visited_refs: &mut HashSet<String>,
502        depth: usize,
503    ) {
504        let type_path = format!("{}/type", path);
505
506        match type_val {
507            Value::String(type_name) => {
508                self.validate_single_type(type_name, obj, root_schema, locator, result, path, enabled_extensions, depth);
509            }
510            Value::Array(types) => {
511                // Union type: ["string", "null"] or [{"$ref": "..."}, "null"]
512                if types.is_empty() {
513                    result.add_error(ValidationError::schema_error(
514                        SchemaErrorCode::SchemaTypeArrayEmpty,
515                        "Union type array cannot be empty",
516                        &type_path,
517                        locator.get_location(&type_path),
518                    ));
519                    return;
520                }
521                for (i, t) in types.iter().enumerate() {
522                    let elem_path = format!("{}/{}", type_path, i);
523                    match t {
524                        Value::String(s) => {
525                            if !is_valid_type(s) {
526                                result.add_error(ValidationError::schema_error(
527                                    SchemaErrorCode::SchemaTypeInvalid,
528                                    format!("Unknown type in union: '{}'", s),
529                                    &elem_path,
530                                    locator.get_location(&elem_path),
531                                ));
532                            }
533                        }
534                        Value::Object(ref_obj) => {
535                            if let Some(ref_val) = ref_obj.get("$ref") {
536                                if let Value::String(ref_str) = ref_val {
537                                    // Validate the ref exists
538                                    self.validate_type_ref(ref_str, root_schema, locator, result, &elem_path, visited_refs);
539                                } else {
540                                    result.add_error(ValidationError::schema_error(
541                                        SchemaErrorCode::SchemaRefNotString,
542                                        "$ref must be a string",
543                                        &format!("{}/$ref", elem_path),
544                                        locator.get_location(&format!("{}/$ref", elem_path)),
545                                    ));
546                                }
547                            } else {
548                                result.add_error(ValidationError::schema_error(
549                                    SchemaErrorCode::SchemaTypeObjectMissingRef,
550                                    "Union type object must have $ref",
551                                    &elem_path,
552                                    locator.get_location(&elem_path),
553                                ));
554                            }
555                        }
556                        _ => {
557                            result.add_error(ValidationError::schema_error(
558                                SchemaErrorCode::SchemaKeywordInvalidType,
559                                "Union type elements must be strings or $ref objects",
560                                &elem_path,
561                                locator.get_location(&elem_path),
562                            ));
563                        }
564                    }
565                }
566            }
567            Value::Object(ref_obj) => {
568                // Type can be an object with $ref
569                if let Some(ref_val) = ref_obj.get("$ref") {
570                    if let Value::String(ref_str) = ref_val {
571                        // Validate the ref exists
572                        self.validate_type_ref(ref_str, root_schema, locator, result, path, visited_refs);
573                    } else {
574                        result.add_error(ValidationError::schema_error(
575                            SchemaErrorCode::SchemaRefNotString,
576                            "$ref must be a string",
577                            &format!("{}/$ref", type_path),
578                            locator.get_location(&format!("{}/$ref", type_path)),
579                        ));
580                    }
581                } else {
582                    result.add_error(ValidationError::schema_error(
583                        SchemaErrorCode::SchemaTypeObjectMissingRef,
584                        "type object must have $ref",
585                        &type_path,
586                        locator.get_location(&type_path),
587                    ));
588                }
589            }
590            _ => {
591                result.add_error(ValidationError::schema_error(
592                    SchemaErrorCode::SchemaKeywordInvalidType,
593                    "type must be a string, array, or object with $ref",
594                    &type_path,
595                    locator.get_location(&type_path),
596                ));
597            }
598        }
599    }
600
601    /// Validates a $ref inside a type attribute.
602    fn validate_type_ref(
603        &self,
604        ref_str: &str,
605        root_schema: &Value,
606        locator: &JsonSourceLocator,
607        result: &mut ValidationResult,
608        path: &str,
609        visited_refs: &mut HashSet<String>,
610    ) {
611        let ref_path = format!("{}/type/$ref", path);
612        
613        if ref_str.starts_with("#/definitions/") {
614            // Check for circular reference
615            if visited_refs.contains(ref_str) {
616                // Check if it's a direct circular reference
617                if let Some(resolved) = self.resolve_ref(ref_str, root_schema) {
618                    if let Value::Object(def_obj) = resolved {
619                        let keys: Vec<&String> = def_obj.keys().collect();
620                        let is_type_ref_only = keys.len() == 1 && keys[0] == "type" && {
621                            if let Some(Value::Object(type_obj)) = def_obj.get("type") {
622                                type_obj.len() == 1 && type_obj.contains_key("$ref")
623                            } else {
624                                false
625                            }
626                        };
627                        
628                        if is_type_ref_only {
629                            result.add_error(ValidationError::schema_error(
630                                SchemaErrorCode::SchemaRefCircular,
631                                format!("Direct circular reference: {}", ref_str),
632                                &ref_path,
633                                locator.get_location(&ref_path),
634                            ));
635                        }
636                    }
637                }
638                return;
639            }
640
641            // Track this ref
642            visited_refs.insert(ref_str.to_string());
643            
644            // Resolve and validate
645            if let Some(resolved) = self.resolve_ref(ref_str, root_schema) {
646                // Check if the resolved schema itself has a type with $ref (for circular detection)
647                if let Value::Object(def_obj) = resolved {
648                    if let Some(type_val) = def_obj.get("type") {
649                        if let Value::Object(type_obj) = type_val {
650                            if let Some(Value::String(inner_ref)) = type_obj.get("$ref") {
651                                self.validate_type_ref(inner_ref, root_schema, locator, result, path, visited_refs);
652                            }
653                        }
654                    }
655                }
656            } else {
657                result.add_error(ValidationError::schema_error(
658                    SchemaErrorCode::SchemaRefNotFound,
659                    format!("Reference not found: {}", ref_str),
660                    &ref_path,
661                    locator.get_location(&ref_path),
662                ));
663            }
664            
665            visited_refs.remove(ref_str);
666        }
667    }
668
669    /// Validates a single type name.
670    fn validate_single_type(
671        &self,
672        type_name: &str,
673        obj: &serde_json::Map<String, Value>,
674        root_schema: &Value,
675        locator: &JsonSourceLocator,
676        result: &mut ValidationResult,
677        path: &str,
678        enabled_extensions: &HashSet<&str>,
679        depth: usize,
680    ) {
681        let type_path = format!("{}/type", path);
682        
683        // Validate type name
684        if !is_valid_type(type_name) {
685            result.add_error(ValidationError::schema_error(
686                SchemaErrorCode::SchemaTypeInvalid,
687                format!("Invalid type: {}", type_name),
688                &type_path,
689                locator.get_location(&type_path),
690            ));
691            return;
692        }
693
694        // Type-specific validation
695        match type_name {
696            "object" => self.validate_object_type(obj, root_schema, locator, result, path, enabled_extensions, depth),
697            "array" | "set" => self.validate_array_type(obj, root_schema, locator, result, path, type_name),
698            "map" => self.validate_map_type(obj, root_schema, locator, result, path),
699            "tuple" => self.validate_tuple_type(obj, root_schema, locator, result, path),
700            "choice" => self.validate_choice_type(obj, root_schema, locator, result, path),
701            _ => {
702                // Validate extended constraints for primitive types
703                self.validate_primitive_constraints(type_name, obj, locator, result, path);
704            }
705        }
706    }
707
708    /// Validates primitive type constraints.
709    fn validate_primitive_constraints(
710        &self,
711        type_name: &str,
712        obj: &serde_json::Map<String, Value>,
713        locator: &JsonSourceLocator,
714        result: &mut ValidationResult,
715        path: &str,
716    ) {
717        use crate::types::is_numeric_type;
718
719        // Check for type-constraint mismatches
720        let is_numeric = is_numeric_type(type_name);
721        let is_string = type_name == "string";
722
723        // minimum/maximum only apply to numeric types
724        if obj.contains_key("minimum") && !is_numeric {
725            result.add_error(ValidationError::schema_error(
726                SchemaErrorCode::SchemaConstraintTypeMismatch,
727                format!("minimum constraint cannot be used with type '{}'", type_name),
728                &format!("{}/minimum", path),
729                locator.get_location(&format!("{}/minimum", path)),
730            ));
731        }
732        if obj.contains_key("maximum") && !is_numeric {
733            result.add_error(ValidationError::schema_error(
734                SchemaErrorCode::SchemaConstraintTypeMismatch,
735                format!("maximum constraint cannot be used with type '{}'", type_name),
736                &format!("{}/maximum", path),
737                locator.get_location(&format!("{}/maximum", path)),
738            ));
739        }
740
741        // minLength/maxLength only apply to string
742        if obj.contains_key("minLength") && !is_string {
743            result.add_error(ValidationError::schema_error(
744                SchemaErrorCode::SchemaConstraintTypeMismatch,
745                format!("minLength constraint cannot be used with type '{}'", type_name),
746                &format!("{}/minLength", path),
747                locator.get_location(&format!("{}/minLength", path)),
748            ));
749        }
750        if obj.contains_key("maxLength") && !is_string {
751            result.add_error(ValidationError::schema_error(
752                SchemaErrorCode::SchemaConstraintTypeMismatch,
753                format!("maxLength constraint cannot be used with type '{}'", type_name),
754                &format!("{}/maxLength", path),
755                locator.get_location(&format!("{}/maxLength", path)),
756            ));
757        }
758
759        // Validate numeric constraint values
760        if is_numeric {
761            self.validate_numeric_constraints(obj, locator, result, path);
762        }
763
764        // Validate string constraint values
765        if is_string {
766            self.validate_string_constraints(obj, locator, result, path);
767        }
768
769        // multipleOf only applies to numeric types
770        if let Some(multiple_of) = obj.get("multipleOf") {
771            if !is_numeric {
772                result.add_error(ValidationError::schema_error(
773                    SchemaErrorCode::SchemaConstraintTypeMismatch,
774                    format!("multipleOf constraint cannot be used with type '{}'", type_name),
775                    &format!("{}/multipleOf", path),
776                    locator.get_location(&format!("{}/multipleOf", path)),
777                ));
778            } else if let Some(n) = multiple_of.as_f64() {
779                if n <= 0.0 {
780                    result.add_error(ValidationError::schema_error(
781                        SchemaErrorCode::SchemaMultipleOfInvalid,
782                        "multipleOf must be greater than 0",
783                        &format!("{}/multipleOf", path),
784                        locator.get_location(&format!("{}/multipleOf", path)),
785                    ));
786                }
787            }
788        }
789
790        // Validate pattern regex
791        if let Some(Value::String(pattern)) = obj.get("pattern") {
792            if regex::Regex::new(pattern).is_err() {
793                result.add_error(ValidationError::schema_error(
794                    SchemaErrorCode::SchemaPatternInvalid,
795                    format!("Invalid regular expression pattern: {}", pattern),
796                    &format!("{}/pattern", path),
797                    locator.get_location(&format!("{}/pattern", path)),
798                ));
799            }
800        }
801    }
802
803    /// Validates numeric constraints (minimum/maximum relationships).
804    fn validate_numeric_constraints(
805        &self,
806        obj: &serde_json::Map<String, Value>,
807        locator: &JsonSourceLocator,
808        result: &mut ValidationResult,
809        path: &str,
810    ) {
811        let minimum = obj.get("minimum").and_then(Value::as_f64);
812        let maximum = obj.get("maximum").and_then(Value::as_f64);
813        
814        if let (Some(min), Some(max)) = (minimum, maximum) {
815            if min > max {
816                result.add_error(ValidationError::schema_error(
817                    SchemaErrorCode::SchemaMinimumExceedsMaximum,
818                    format!("minimum ({}) exceeds maximum ({})", min, max),
819                    &format!("{}/minimum", path),
820                    locator.get_location(&format!("{}/minimum", path)),
821                ));
822            }
823        }
824    }
825
826    /// Validates string constraints (minLength/maxLength relationships).
827    fn validate_string_constraints(
828        &self,
829        obj: &serde_json::Map<String, Value>,
830        locator: &JsonSourceLocator,
831        result: &mut ValidationResult,
832        path: &str,
833    ) {
834        let min_length = obj.get("minLength").and_then(Value::as_i64);
835        let max_length = obj.get("maxLength").and_then(Value::as_i64);
836
837        if let Some(min) = min_length {
838            if min < 0 {
839                result.add_error(ValidationError::schema_error(
840                    SchemaErrorCode::SchemaMinLengthNegative,
841                    "minLength cannot be negative",
842                    &format!("{}/minLength", path),
843                    locator.get_location(&format!("{}/minLength", path)),
844                ));
845            }
846        }
847
848        if let Some(max) = max_length {
849            if max < 0 {
850                result.add_error(ValidationError::schema_error(
851                    SchemaErrorCode::SchemaMaxLengthNegative,
852                    "maxLength cannot be negative",
853                    &format!("{}/maxLength", path),
854                    locator.get_location(&format!("{}/maxLength", path)),
855                ));
856            }
857        }
858
859        if let (Some(min), Some(max)) = (min_length, max_length) {
860            if min > max {
861                result.add_error(ValidationError::schema_error(
862                    SchemaErrorCode::SchemaMinLengthExceedsMaxLength,
863                    format!("minLength ({}) exceeds maxLength ({})", min, max),
864                    &format!("{}/minLength", path),
865                    locator.get_location(&format!("{}/minLength", path)),
866                ));
867            }
868        }
869    }
870
871    /// Validates object type requirements.
872    fn validate_object_type(
873        &self,
874        obj: &serde_json::Map<String, Value>,
875        root_schema: &Value,
876        locator: &JsonSourceLocator,
877        result: &mut ValidationResult,
878        path: &str,
879        _enabled_extensions: &HashSet<&str>,
880        depth: usize,
881    ) {
882        // Validate properties
883        if let Some(props) = obj.get("properties") {
884            let props_path = format!("{}/properties", path);
885            match props {
886                Value::Object(props_obj) => {
887                    for (prop_name, prop_schema) in props_obj {
888                        let prop_path = format!("{}/{}", props_path, prop_name);
889                        self.validate_schema(
890                            prop_schema,
891                            root_schema,
892                            locator,
893                            result,
894                            &prop_path,
895                            false,
896                            &mut HashSet::new(),
897                            depth + 1,
898                        );
899                    }
900                }
901                _ => {
902                    result.add_error(ValidationError::schema_error(
903                        SchemaErrorCode::SchemaPropertiesMustBeObject,
904                        "properties must be an object",
905                        &props_path,
906                        locator.get_location(&props_path),
907                    ));
908                }
909            }
910        }
911
912        // Validate required
913        if let Some(required) = obj.get("required") {
914            self.validate_required(required, obj.get("properties"), locator, result, path);
915        }
916
917        // Validate additionalProperties
918        if let Some(additional) = obj.get("additionalProperties") {
919            let add_path = format!("{}/additionalProperties", path);
920            match additional {
921                Value::Bool(_) => {}
922                Value::Object(_) => {
923                    self.validate_schema(
924                        additional,
925                        root_schema,
926                        locator,
927                        result,
928                        &add_path,
929                        false,
930                        &mut HashSet::new(),
931                        depth + 1,
932                    );
933                }
934                _ => {
935                    result.add_error(ValidationError::schema_error(
936                        SchemaErrorCode::SchemaAdditionalPropertiesInvalid,
937                        "additionalProperties must be a boolean or schema object",
938                        &add_path,
939                        locator.get_location(&add_path),
940                    ));
941                }
942            }
943        }
944    }
945
946    /// Validates required keyword.
947    fn validate_required(
948        &self,
949        required: &Value,
950        properties: Option<&Value>,
951        locator: &JsonSourceLocator,
952        result: &mut ValidationResult,
953        path: &str,
954    ) {
955        let required_path = format!("{}/required", path);
956
957        match required {
958            Value::Array(arr) => {
959                let mut seen = HashSet::new();
960                for (i, item) in arr.iter().enumerate() {
961                    match item {
962                        Value::String(s) => {
963                            // Check for duplicates
964                            if !seen.insert(s.clone()) {
965                                result.add_error(ValidationError::schema_warning(
966                                    SchemaErrorCode::SchemaRequiredPropertyNotDefined,
967                                    format!("Duplicate required property: {}", s),
968                                    &format!("{}/{}", required_path, i),
969                                    locator.get_location(&format!("{}/{}", required_path, i)),
970                                ));
971                            }
972
973                            // Check if property exists
974                            if let Some(Value::Object(props)) = properties {
975                                if !props.contains_key(s) {
976                                    result.add_error(ValidationError::schema_error(
977                                        SchemaErrorCode::SchemaRequiredPropertyNotDefined,
978                                        format!("Required property not defined: {}", s),
979                                        &format!("{}/{}", required_path, i),
980                                        locator.get_location(&format!("{}/{}", required_path, i)),
981                                    ));
982                                }
983                            }
984                        }
985                        _ => {
986                            result.add_error(ValidationError::schema_error(
987                                SchemaErrorCode::SchemaRequiredItemMustBeString,
988                                "Required item must be a string",
989                                &format!("{}/{}", required_path, i),
990                                locator.get_location(&format!("{}/{}", required_path, i)),
991                            ));
992                        }
993                    }
994                }
995            }
996            _ => {
997                result.add_error(ValidationError::schema_error(
998                    SchemaErrorCode::SchemaRequiredMustBeArray,
999                    "required must be an array",
1000                    &required_path,
1001                    locator.get_location(&required_path),
1002                ));
1003            }
1004        }
1005    }
1006
1007    /// Validates array/set type requirements.
1008    fn validate_array_type(
1009        &self,
1010        obj: &serde_json::Map<String, Value>,
1011        root_schema: &Value,
1012        locator: &JsonSourceLocator,
1013        result: &mut ValidationResult,
1014        path: &str,
1015        type_name: &str,
1016    ) {
1017        // array and set require items
1018        if !obj.contains_key("items") {
1019            result.add_error(ValidationError::schema_error(
1020                SchemaErrorCode::SchemaArrayMissingItems,
1021                format!("{} type requires items keyword", type_name),
1022                path,
1023                locator.get_location(path),
1024            ));
1025        } else if let Some(items) = obj.get("items") {
1026            let items_path = format!("{}/items", path);
1027            self.validate_schema(items, root_schema, locator, result, &items_path, false, &mut HashSet::new(), 0);
1028        }
1029
1030        // Validate minItems/maxItems constraints
1031        let min_items = obj.get("minItems").and_then(Value::as_i64);
1032        let max_items = obj.get("maxItems").and_then(Value::as_i64);
1033
1034        if let Some(min) = min_items {
1035            if min < 0 {
1036                result.add_error(ValidationError::schema_error(
1037                    SchemaErrorCode::SchemaMinItemsNegative,
1038                    "minItems cannot be negative",
1039                    &format!("{}/minItems", path),
1040                    locator.get_location(&format!("{}/minItems", path)),
1041                ));
1042            }
1043        }
1044
1045        if let (Some(min), Some(max)) = (min_items, max_items) {
1046            if min > max {
1047                result.add_error(ValidationError::schema_error(
1048                    SchemaErrorCode::SchemaMinItemsExceedsMaxItems,
1049                    format!("minItems ({}) exceeds maxItems ({})", min, max),
1050                    &format!("{}/minItems", path),
1051                    locator.get_location(&format!("{}/minItems", path)),
1052                ));
1053            }
1054        }
1055    }
1056
1057    /// Validates map type requirements.
1058    fn validate_map_type(
1059        &self,
1060        obj: &serde_json::Map<String, Value>,
1061        root_schema: &Value,
1062        locator: &JsonSourceLocator,
1063        result: &mut ValidationResult,
1064        path: &str,
1065    ) {
1066        // map requires values
1067        if !obj.contains_key("values") {
1068            result.add_error(ValidationError::schema_error(
1069                SchemaErrorCode::SchemaMapMissingValues,
1070                "map type requires values keyword",
1071                path,
1072                locator.get_location(path),
1073            ));
1074        } else if let Some(values) = obj.get("values") {
1075            let values_path = format!("{}/values", path);
1076            self.validate_schema(values, root_schema, locator, result, &values_path, false, &mut HashSet::new(), 0);
1077        }
1078    }
1079
1080    /// Validates tuple type requirements.
1081    fn validate_tuple_type(
1082        &self,
1083        obj: &serde_json::Map<String, Value>,
1084        _root_schema: &Value,
1085        locator: &JsonSourceLocator,
1086        result: &mut ValidationResult,
1087        path: &str,
1088    ) {
1089        // tuple requires properties and tuple keywords
1090        let has_properties = obj.contains_key("properties");
1091        let has_tuple = obj.contains_key("tuple");
1092
1093        if !has_properties || !has_tuple {
1094            result.add_error(ValidationError::schema_error(
1095                SchemaErrorCode::SchemaTupleMissingDefinition,
1096                "tuple type requires both properties and tuple keywords",
1097                path,
1098                locator.get_location(path),
1099            ));
1100            return;
1101        }
1102
1103        // Validate tuple is an array of strings
1104        if let Some(tuple) = obj.get("tuple") {
1105            let tuple_path = format!("{}/tuple", path);
1106            match tuple {
1107                Value::Array(arr) => {
1108                    let properties = obj.get("properties").and_then(Value::as_object);
1109                    
1110                    for (i, item) in arr.iter().enumerate() {
1111                        match item {
1112                            Value::String(s) => {
1113                                // Check property exists
1114                                if let Some(props) = properties {
1115                                    if !props.contains_key(s) {
1116                                        result.add_error(ValidationError::schema_error(
1117                                            SchemaErrorCode::SchemaTuplePropertyNotDefined,
1118                                            format!("Tuple element references undefined property: {}", s),
1119                                            &format!("{}/{}", tuple_path, i),
1120                                            locator.get_location(&format!("{}/{}", tuple_path, i)),
1121                                        ));
1122                                    }
1123                                }
1124                            }
1125                            _ => {
1126                                result.add_error(ValidationError::schema_error(
1127                                    SchemaErrorCode::SchemaTupleInvalidFormat,
1128                                    "Tuple element must be a string (property name)",
1129                                    &format!("{}/{}", tuple_path, i),
1130                                    locator.get_location(&format!("{}/{}", tuple_path, i)),
1131                                ));
1132                            }
1133                        }
1134                    }
1135                }
1136                _ => {
1137                    result.add_error(ValidationError::schema_error(
1138                        SchemaErrorCode::SchemaTupleInvalidFormat,
1139                        "tuple must be an array",
1140                        &tuple_path,
1141                        locator.get_location(&tuple_path),
1142                    ));
1143                }
1144            }
1145        }
1146    }
1147
1148    /// Validates choice type requirements.
1149    fn validate_choice_type(
1150        &self,
1151        obj: &serde_json::Map<String, Value>,
1152        root_schema: &Value,
1153        locator: &JsonSourceLocator,
1154        result: &mut ValidationResult,
1155        path: &str,
1156    ) {
1157        // choice requires choices keyword
1158        if !obj.contains_key("choices") {
1159            result.add_error(ValidationError::schema_error(
1160                SchemaErrorCode::SchemaChoiceMissingChoices,
1161                "choice type requires choices keyword",
1162                path,
1163                locator.get_location(path),
1164            ));
1165            return;
1166        }
1167
1168        // Validate choices is an object
1169        if let Some(choices) = obj.get("choices") {
1170            let choices_path = format!("{}/choices", path);
1171            match choices {
1172                Value::Object(choices_obj) => {
1173                    for (choice_name, choice_schema) in choices_obj {
1174                        let choice_path = format!("{}/{}", choices_path, choice_name);
1175                        self.validate_schema(
1176                            choice_schema,
1177                            root_schema,
1178                            locator,
1179                            result,
1180                            &choice_path,
1181                            false,
1182                            &mut HashSet::new(),
1183                            0,
1184                        );
1185                    }
1186                }
1187                _ => {
1188                    result.add_error(ValidationError::schema_error(
1189                        SchemaErrorCode::SchemaChoicesNotObject,
1190                        "choices must be an object",
1191                        &choices_path,
1192                        locator.get_location(&choices_path),
1193                    ));
1194                }
1195            }
1196        }
1197
1198        // Validate selector if present
1199        if let Some(selector) = obj.get("selector") {
1200            let selector_path = format!("{}/selector", path);
1201            if !selector.is_string() {
1202                result.add_error(ValidationError::schema_error(
1203                    SchemaErrorCode::SchemaSelectorNotString,
1204                    "selector must be a string",
1205                    &selector_path,
1206                    locator.get_location(&selector_path),
1207                ));
1208            }
1209        }
1210    }
1211
1212    /// Validates definitions.
1213    fn validate_definitions(
1214        &self,
1215        defs: &Value,
1216        root_schema: &Value,
1217        locator: &JsonSourceLocator,
1218        result: &mut ValidationResult,
1219        path: &str,
1220        visited_refs: &mut HashSet<String>,
1221        depth: usize,
1222    ) {
1223        let defs_path = format!("{}/definitions", path);
1224
1225        match defs {
1226            Value::Object(defs_obj) => {
1227                for (def_name, def_schema) in defs_obj {
1228                    let def_path = format!("{}/{}", defs_path, def_name);
1229                    self.validate_definition_or_namespace(def_schema, root_schema, locator, result, &def_path, visited_refs, depth);
1230                }
1231            }
1232            _ => {
1233                result.add_error(ValidationError::schema_error(
1234                    SchemaErrorCode::SchemaDefinitionsMustBeObject,
1235                    "definitions must be an object",
1236                    &defs_path,
1237                    locator.get_location(&defs_path),
1238                ));
1239            }
1240        }
1241    }
1242
1243    /// Validates a definition or namespace (recursive for namespaces).
1244    fn validate_definition_or_namespace(
1245        &self,
1246        def_schema: &Value,
1247        root_schema: &Value,
1248        locator: &JsonSourceLocator,
1249        result: &mut ValidationResult,
1250        path: &str,
1251        visited_refs: &mut HashSet<String>,
1252        depth: usize,
1253    ) {
1254        if let Value::Object(def_obj) = def_schema {
1255            // Empty definitions are invalid
1256            if def_obj.is_empty() {
1257                result.add_error(ValidationError::schema_error(
1258                    SchemaErrorCode::SchemaDefinitionMissingType,
1259                    "Definition must have type, $ref, definitions, or composition",
1260                    path,
1261                    locator.get_location(path),
1262                ));
1263                return;
1264            }
1265
1266            let has_type = def_obj.contains_key("type");
1267            let has_ref = def_obj.contains_key("$ref");
1268            let has_definitions = def_obj.contains_key("definitions");
1269            let has_composition = def_obj.keys().any(|k| 
1270                ["allOf", "anyOf", "oneOf", "not", "if"].contains(&k.as_str())
1271            );
1272            
1273            if has_type || has_ref || has_definitions || has_composition {
1274                // This is a type definition - validate it
1275                self.validate_schema_internal(
1276                    def_schema,
1277                    root_schema,
1278                    locator,
1279                    result,
1280                    path,
1281                    false,
1282                    visited_refs,
1283                    depth + 1,
1284                );
1285            } else {
1286                // This might be a namespace - check if all children look like schemas
1287                // (objects with type, $ref, etc.)
1288                let is_namespace = def_obj.values().all(|v| {
1289                    if let Value::Object(child) = v {
1290                        child.contains_key("type") 
1291                            || child.contains_key("$ref") 
1292                            || child.contains_key("definitions")
1293                            || child.keys().any(|k| ["allOf", "anyOf", "oneOf"].contains(&k.as_str()))
1294                            || child.values().all(|cv| cv.is_object())  // nested namespace
1295                    } else {
1296                        false
1297                    }
1298                });
1299                
1300                if is_namespace {
1301                    // Recursively validate as namespace
1302                    for (child_name, child_schema) in def_obj {
1303                        let child_path = format!("{}/{}", path, child_name);
1304                        self.validate_definition_or_namespace(child_schema, root_schema, locator, result, &child_path, visited_refs, depth + 1);
1305                    }
1306                } else {
1307                    // Not a namespace and no type - error
1308                    result.add_error(ValidationError::schema_error(
1309                        SchemaErrorCode::SchemaDefinitionMissingType,
1310                        "Definition must have type, $ref, definitions, or composition",
1311                        path,
1312                        locator.get_location(path),
1313                    ));
1314                }
1315            }
1316        } else {
1317            result.add_error(ValidationError::schema_error(
1318                SchemaErrorCode::SchemaDefinitionInvalid,
1319                "Definition must be an object",
1320                path,
1321                locator.get_location(path),
1322            ));
1323        }
1324    }
1325
1326    /// Validates enum keyword.
1327    fn validate_enum(
1328        &self,
1329        enum_val: &Value,
1330        locator: &JsonSourceLocator,
1331        result: &mut ValidationResult,
1332        path: &str,
1333    ) {
1334        let enum_path = format!("{}/enum", path);
1335
1336        match enum_val {
1337            Value::Array(arr) => {
1338                if arr.is_empty() {
1339                    result.add_error(ValidationError::schema_error(
1340                        SchemaErrorCode::SchemaEnumEmpty,
1341                        "enum cannot be empty",
1342                        &enum_path,
1343                        locator.get_location(&enum_path),
1344                    ));
1345                    return;
1346                }
1347
1348                // Check for duplicates
1349                let mut seen = Vec::new();
1350                for (i, item) in arr.iter().enumerate() {
1351                    let item_str = item.to_string();
1352                    if seen.contains(&item_str) {
1353                        result.add_error(ValidationError::schema_error(
1354                            SchemaErrorCode::SchemaEnumDuplicates,
1355                            "enum contains duplicate values",
1356                            &format!("{}/{}", enum_path, i),
1357                            locator.get_location(&format!("{}/{}", enum_path, i)),
1358                        ));
1359                    } else {
1360                        seen.push(item_str);
1361                    }
1362                }
1363            }
1364            _ => {
1365                result.add_error(ValidationError::schema_error(
1366                    SchemaErrorCode::SchemaEnumNotArray,
1367                    "enum must be an array",
1368                    &enum_path,
1369                    locator.get_location(&enum_path),
1370                ));
1371            }
1372        }
1373    }
1374
1375    /// Validates composition keywords.
1376    fn validate_composition(
1377        &self,
1378        obj: &serde_json::Map<String, Value>,
1379        root_schema: &Value,
1380        locator: &JsonSourceLocator,
1381        result: &mut ValidationResult,
1382        path: &str,
1383        _enabled_extensions: &HashSet<&str>,
1384        visited_refs: &mut HashSet<String>,
1385        depth: usize,
1386    ) {
1387        // allOf
1388        if let Some(all_of) = obj.get("allOf") {
1389            self.validate_composition_array(all_of, "allOf", root_schema, locator, result, path, visited_refs, depth);
1390        }
1391
1392        // anyOf
1393        if let Some(any_of) = obj.get("anyOf") {
1394            self.validate_composition_array(any_of, "anyOf", root_schema, locator, result, path, visited_refs, depth);
1395        }
1396
1397        // oneOf
1398        if let Some(one_of) = obj.get("oneOf") {
1399            self.validate_composition_array(one_of, "oneOf", root_schema, locator, result, path, visited_refs, depth);
1400        }
1401
1402        // not
1403        if let Some(not) = obj.get("not") {
1404            let not_path = format!("{}/not", path);
1405            self.validate_schema_internal(not, root_schema, locator, result, &not_path, false, visited_refs, depth + 1);
1406        }
1407
1408        // if/then/else
1409        if let Some(if_schema) = obj.get("if") {
1410            let if_path = format!("{}/if", path);
1411            self.validate_schema_internal(if_schema, root_schema, locator, result, &if_path, false, visited_refs, depth + 1);
1412        }
1413
1414        if let Some(then_schema) = obj.get("then") {
1415            if !obj.contains_key("if") {
1416                result.add_error(ValidationError::schema_error(
1417                    SchemaErrorCode::SchemaThenWithoutIf,
1418                    "then requires if",
1419                    &format!("{}/then", path),
1420                    locator.get_location(&format!("{}/then", path)),
1421                ));
1422            }
1423            let then_path = format!("{}/then", path);
1424            self.validate_schema_internal(then_schema, root_schema, locator, result, &then_path, false, visited_refs, depth + 1);
1425        }
1426
1427        if let Some(else_schema) = obj.get("else") {
1428            if !obj.contains_key("if") {
1429                result.add_error(ValidationError::schema_error(
1430                    SchemaErrorCode::SchemaElseWithoutIf,
1431                    "else requires if",
1432                    &format!("{}/else", path),
1433                    locator.get_location(&format!("{}/else", path)),
1434                ));
1435            }
1436            let else_path = format!("{}/else", path);
1437            self.validate_schema_internal(else_schema, root_schema, locator, result, &else_path, false, visited_refs, depth + 1);
1438        }
1439    }
1440
1441    /// Validates a composition array (allOf, anyOf, oneOf).
1442    fn validate_composition_array(
1443        &self,
1444        value: &Value,
1445        keyword: &str,
1446        root_schema: &Value,
1447        locator: &JsonSourceLocator,
1448        result: &mut ValidationResult,
1449        path: &str,
1450        visited_refs: &mut HashSet<String>,
1451        depth: usize,
1452    ) {
1453        let keyword_path = format!("{}/{}", path, keyword);
1454
1455        match value {
1456            Value::Array(arr) => {
1457                // Check for empty array
1458                if arr.is_empty() {
1459                    result.add_error(ValidationError::schema_error(
1460                        SchemaErrorCode::SchemaKeywordInvalidType,
1461                        format!("{} array cannot be empty", keyword),
1462                        &keyword_path,
1463                        locator.get_location(&keyword_path),
1464                    ));
1465                    return;
1466                }
1467                
1468                for (i, item) in arr.iter().enumerate() {
1469                    let item_path = format!("{}/{}", keyword_path, i);
1470                    self.validate_schema_internal(item, root_schema, locator, result, &item_path, false, visited_refs, depth + 1);
1471                }
1472            }
1473            _ => {
1474                let code = match keyword {
1475                    "allOf" => SchemaErrorCode::SchemaAllOfNotArray,
1476                    "anyOf" => SchemaErrorCode::SchemaAnyOfNotArray,
1477                    "oneOf" => SchemaErrorCode::SchemaOneOfNotArray,
1478                    _ => SchemaErrorCode::SchemaAllOfNotArray,
1479                };
1480                result.add_error(ValidationError::schema_error(
1481                    code,
1482                    format!("{} must be an array", keyword),
1483                    &keyword_path,
1484                    locator.get_location(&keyword_path),
1485                ));
1486            }
1487        }
1488    }
1489
1490    /// Validates $extends keyword.
1491    fn validate_extends(
1492        &self,
1493        extends_val: &Value,
1494        root_schema: &Value,
1495        locator: &JsonSourceLocator,
1496        result: &mut ValidationResult,
1497        path: &str,
1498        visited_refs: &mut HashSet<String>,
1499        _depth: usize,
1500    ) {
1501        let extends_path = format!("{}/$extends", path);
1502        
1503        // $extends can be a string or an array of strings
1504        let refs: Vec<(String, String)> = match extends_val {
1505            Value::String(s) => {
1506                if s.is_empty() {
1507                    result.add_error(ValidationError::schema_error(
1508                        SchemaErrorCode::SchemaExtendsEmpty,
1509                        "$extends cannot be empty",
1510                        &extends_path,
1511                        locator.get_location(&extends_path),
1512                    ));
1513                    return;
1514                }
1515                vec![(s.clone(), extends_path.clone())]
1516            }
1517            Value::Array(arr) => {
1518                if arr.is_empty() {
1519                    result.add_error(ValidationError::schema_error(
1520                        SchemaErrorCode::SchemaExtendsEmpty,
1521                        "$extends array cannot be empty",
1522                        &extends_path,
1523                        locator.get_location(&extends_path),
1524                    ));
1525                    return;
1526                }
1527                let mut refs = Vec::new();
1528                for (i, item) in arr.iter().enumerate() {
1529                    let item_path = format!("{}/{}", extends_path, i);
1530                    if let Value::String(s) = item {
1531                        if s.is_empty() {
1532                            result.add_error(ValidationError::schema_error(
1533                                SchemaErrorCode::SchemaExtendsEmpty,
1534                                "$extends array items cannot be empty",
1535                                &item_path,
1536                                locator.get_location(&item_path),
1537                            ));
1538                        } else {
1539                            refs.push((s.clone(), item_path));
1540                        }
1541                    } else {
1542                        result.add_error(ValidationError::schema_error(
1543                            SchemaErrorCode::SchemaExtendsNotString,
1544                            "$extends array items must be strings",
1545                            &item_path,
1546                            locator.get_location(&item_path),
1547                        ));
1548                    }
1549                }
1550                refs
1551            }
1552            _ => {
1553                result.add_error(ValidationError::schema_error(
1554                    SchemaErrorCode::SchemaExtendsNotString,
1555                    "$extends must be a string or array of strings",
1556                    &extends_path,
1557                    locator.get_location(&extends_path),
1558                ));
1559                return;
1560            }
1561        };
1562
1563        // Validate each reference
1564        for (ref_str, ref_path) in refs {
1565            // Check for circular reference
1566            if visited_refs.contains(&ref_str) {
1567                result.add_error(ValidationError::schema_error(
1568                    SchemaErrorCode::SchemaExtendsCircular,
1569                    format!("Circular $extends reference: {}", ref_str),
1570                    &ref_path,
1571                    locator.get_location(&ref_path),
1572                ));
1573                continue;
1574            }
1575
1576            // Resolve reference
1577            if ref_str.starts_with("#/definitions/") && self.resolve_ref(&ref_str, root_schema).is_none() {
1578                result.add_error(ValidationError::schema_error(
1579                    SchemaErrorCode::SchemaExtendsNotFound,
1580                    format!("$extends reference not found: {}", ref_str),
1581                    &ref_path,
1582                    locator.get_location(&ref_path),
1583                ));
1584            }
1585            // External references would be validated here if import is enabled
1586        }
1587    }
1588
1589    /// Validates altnames keyword.
1590    fn validate_altnames(
1591        &self,
1592        altnames_val: &Value,
1593        locator: &JsonSourceLocator,
1594        result: &mut ValidationResult,
1595        path: &str,
1596    ) {
1597        let altnames_path = format!("{}/altnames", path);
1598        
1599        match altnames_val {
1600            Value::Object(obj) => {
1601                for (key, value) in obj {
1602                    if !value.is_string() {
1603                        result.add_error(ValidationError::schema_error(
1604                            SchemaErrorCode::SchemaAltnamesValueNotString,
1605                            format!("altnames value for '{}' must be a string", key),
1606                            &format!("{}/{}", altnames_path, key),
1607                            locator.get_location(&format!("{}/{}", altnames_path, key)),
1608                        ));
1609                    }
1610                }
1611            }
1612            _ => {
1613                result.add_error(ValidationError::schema_error(
1614                    SchemaErrorCode::SchemaAltnamesNotObject,
1615                    "altnames must be an object",
1616                    &altnames_path,
1617                    locator.get_location(&altnames_path),
1618                ));
1619            }
1620        }
1621    }
1622
1623    /// Checks for extension keywords used without enabling the extension.
1624    fn check_extension_keywords(
1625        &self,
1626        obj: &serde_json::Map<String, Value>,
1627        locator: &JsonSourceLocator,
1628        result: &mut ValidationResult,
1629        path: &str,
1630        enabled_extensions: &HashSet<&str>,
1631    ) {
1632        let validation_enabled = enabled_extensions.contains("JSONStructureValidation");
1633        let composition_enabled = enabled_extensions.contains("JSONStructureConditionalComposition");
1634
1635        for (key, _) in obj {
1636            // Check validation keywords
1637            if VALIDATION_EXTENSION_KEYWORDS.contains(&key.as_str()) && !validation_enabled {
1638                result.add_error(ValidationError::schema_warning(
1639                    SchemaErrorCode::SchemaExtensionKeywordWithoutUses,
1640                    format!(
1641                        "Validation extension keyword '{}' is used but validation extensions are not enabled. \
1642                        Add '\"$uses\": [\"JSONStructureValidation\"]' to enable validation, or this keyword will be ignored.",
1643                        key
1644                    ),
1645                    &format!("{}/{}", path, key),
1646                    locator.get_location(&format!("{}/{}", path, key)),
1647                ));
1648            }
1649
1650            // Check composition keywords
1651            if COMPOSITION_KEYWORDS.contains(&key.as_str()) && !composition_enabled {
1652                result.add_error(ValidationError::schema_warning(
1653                    SchemaErrorCode::SchemaExtensionKeywordWithoutUses,
1654                    format!(
1655                        "Conditional composition keyword '{}' is used but extensions are not enabled. \
1656                        Add '\"$uses\": [\"JSONStructureConditionalComposition\"]' to enable.",
1657                        key
1658                    ),
1659                    &format!("{}/{}", path, key),
1660                    locator.get_location(&format!("{}/{}", path, key)),
1661                ));
1662            }
1663        }
1664    }
1665}
1666
1667#[cfg(test)]
1668mod tests {
1669    use super::*;
1670
1671    #[test]
1672    fn test_valid_simple_schema() {
1673        let schema = r#"{
1674            "$id": "https://example.com/schema",
1675            "name": "TestSchema",
1676            "type": "string"
1677        }"#;
1678        
1679        let validator = SchemaValidator::new();
1680        let result = validator.validate(schema);
1681        assert!(result.is_valid());
1682    }
1683
1684    #[test]
1685    fn test_missing_id() {
1686        let schema = r#"{
1687            "name": "TestSchema",
1688            "type": "string"
1689        }"#;
1690        
1691        let validator = SchemaValidator::new();
1692        let result = validator.validate(schema);
1693        assert!(!result.is_valid());
1694    }
1695
1696    #[test]
1697    fn test_missing_name_with_type() {
1698        let schema = r#"{
1699            "$id": "https://example.com/schema",
1700            "type": "string"
1701        }"#;
1702        
1703        let validator = SchemaValidator::new();
1704        let result = validator.validate(schema);
1705        assert!(!result.is_valid());
1706    }
1707
1708    #[test]
1709    fn test_invalid_type() {
1710        let schema = r#"{
1711            "$id": "https://example.com/schema",
1712            "name": "TestSchema",
1713            "type": "invalid_type"
1714        }"#;
1715        
1716        let validator = SchemaValidator::new();
1717        let result = validator.validate(schema);
1718        assert!(!result.is_valid());
1719    }
1720
1721    #[test]
1722    fn test_array_missing_items() {
1723        let schema = r#"{
1724            "$id": "https://example.com/schema",
1725            "name": "TestSchema",
1726            "type": "array"
1727        }"#;
1728        
1729        let validator = SchemaValidator::new();
1730        let result = validator.validate(schema);
1731        assert!(!result.is_valid());
1732    }
1733
1734    #[test]
1735    fn test_map_missing_values() {
1736        let schema = r#"{
1737            "$id": "https://example.com/schema",
1738            "name": "TestSchema",
1739            "type": "map"
1740        }"#;
1741        
1742        let validator = SchemaValidator::new();
1743        let result = validator.validate(schema);
1744        assert!(!result.is_valid());
1745    }
1746
1747    #[test]
1748    fn test_tuple_valid() {
1749        let schema = r#"{
1750            "$id": "https://example.com/schema",
1751            "name": "TestSchema",
1752            "type": "tuple",
1753            "properties": {
1754                "first": { "type": "string" },
1755                "second": { "type": "int32" }
1756            },
1757            "tuple": ["first", "second"]
1758        }"#;
1759        
1760        let validator = SchemaValidator::new();
1761        let result = validator.validate(schema);
1762        assert!(result.is_valid());
1763    }
1764
1765    #[test]
1766    fn test_choice_valid() {
1767        let schema = r#"{
1768            "$id": "https://example.com/schema",
1769            "name": "TestSchema",
1770            "type": "choice",
1771            "selector": "kind",
1772            "choices": {
1773                "text": { "type": "string" },
1774                "number": { "type": "int32" }
1775            }
1776        }"#;
1777        
1778        let validator = SchemaValidator::new();
1779        let result = validator.validate(schema);
1780        assert!(result.is_valid());
1781    }
1782
1783    #[test]
1784    fn test_enum_empty() {
1785        let schema = r#"{
1786            "$id": "https://example.com/schema",
1787            "name": "TestSchema",
1788            "type": "string",
1789            "enum": []
1790        }"#;
1791        
1792        let validator = SchemaValidator::new();
1793        let result = validator.validate(schema);
1794        assert!(!result.is_valid());
1795    }
1796
1797    #[test]
1798    fn test_ref_to_definition() {
1799        let schema = r##"{
1800            "$id": "https://example.com/schema",
1801            "name": "TestSchema",
1802            "type": "object",
1803            "definitions": {
1804                "Inner": {
1805                    "type": "string"
1806                }
1807            },
1808            "properties": {
1809                "value": { "type": { "$ref": "#/definitions/Inner" } }
1810            }
1811        }"##;
1812        
1813        let validator = SchemaValidator::new();
1814        let result = validator.validate(schema);
1815        for err in result.all_errors() {
1816            println!("Error: {:?}", err);
1817        }
1818        assert!(result.is_valid(), "Schema with valid ref should pass");
1819    }
1820
1821    #[test]
1822    fn test_ref_undefined() {
1823        let schema = r##"{
1824            "$id": "https://example.com/schema",
1825            "name": "TestSchema",
1826            "type": "object",
1827            "properties": {
1828                "value": { "type": { "$ref": "#/definitions/Undefined" } }
1829            }
1830        }"##;
1831        
1832        let validator = SchemaValidator::new();
1833        let result = validator.validate(schema);
1834        assert!(!result.is_valid(), "Schema with undefined ref should fail");
1835    }
1836
1837    #[test]
1838    fn test_union_type() {
1839        let schema = r##"{
1840            "$id": "https://example.com/schema",
1841            "name": "TestSchema",
1842            "type": "object",
1843            "properties": {
1844                "value": { "type": ["string", "null"] }
1845            }
1846        }"##;
1847        
1848        let validator = SchemaValidator::new();
1849        let result = validator.validate(schema);
1850        for err in result.all_errors() {
1851            println!("Error: {:?}", err);
1852        }
1853        assert!(result.is_valid(), "Schema with union type should pass");
1854    }
1855}