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