json_schema_validator_core/
lib.rs

1use serde::{Deserialize, Serialize};
2use serde_json::Value;
3use std::collections::HashMap;
4use std::ffi::{CStr, CString};
5use std::os::raw::c_char;
6use wasm_bindgen::prelude::*;
7use regex::Regex;
8
9#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct ValidationError {
11    pub instance_path: String,
12    pub schema_path: String,
13    pub keyword: String,
14    pub message: String,
15    pub instance_value: Option<Value>,
16    pub schema_value: Option<Value>,
17}
18
19pub struct ValidationOptions {
20    pub draft: SchemaDraft,
21    pub custom_formats: HashMap<String, fn(&str) -> bool>,
22    pub short_circuit: bool,
23    pub collect_annotations: bool,
24}
25
26#[derive(Debug, Clone, Copy)]
27pub enum SchemaDraft {
28    Draft4,
29    Draft6,
30    Draft7,
31    Draft201909,
32    Draft202012,
33}
34
35impl Default for ValidationOptions {
36    fn default() -> Self {
37        Self {
38            draft: SchemaDraft::Draft7,
39            custom_formats: HashMap::new(),
40            short_circuit: false,
41            collect_annotations: false,
42        }
43    }
44}
45
46pub struct JsonSchemaValidator {
47    schema: Value,
48    options: ValidationOptions,
49}
50
51impl JsonSchemaValidator {
52    pub fn new(schema: Value, options: ValidationOptions) -> Result<Self, ValidationError> {
53        let validator = Self {
54            schema,
55            options,
56        };
57        
58        // Pre-validate the schema
59        validator.validate_schema()?;
60        Ok(validator)
61    }
62
63    pub fn validate(&self, instance: &Value) -> Vec<ValidationError> {
64        let mut errors = Vec::new();
65        self.validate_recursive(instance, &self.schema, "", "", &mut errors);
66        errors
67    }
68
69    pub fn is_valid(&self, instance: &Value) -> bool {
70        let mut errors = Vec::new();
71        self.validate_recursive(instance, &self.schema, "", "", &mut errors);
72        errors.is_empty()
73    }
74
75    fn validate_schema(&self) -> Result<(), ValidationError> {
76        // Basic schema validation - ensure it's a valid JSON schema
77        if !self.schema.is_object() {
78            return Err(ValidationError {
79                instance_path: "".to_string(),
80                schema_path: "".to_string(),
81                keyword: "schema".to_string(),
82                message: "Schema must be an object".to_string(),
83                instance_value: None,
84                schema_value: Some(self.schema.clone()),
85            });
86        }
87        Ok(())
88    }
89
90    fn validate_recursive(
91        &self,
92        instance: &Value,
93        schema: &Value,
94        instance_path: &str,
95        schema_path: &str,
96        errors: &mut Vec<ValidationError>,
97    ) {
98        if self.options.short_circuit && !errors.is_empty() {
99            return;
100        }
101
102        let schema_obj = match schema.as_object() {
103            Some(obj) => obj,
104            None => return,
105        };
106
107        // Type validation
108        if let Some(type_value) = schema_obj.get("type") {
109            self.validate_type(instance, type_value, instance_path, schema_path, errors);
110        }
111
112        // String validations
113        if instance.is_string() {
114            let string_val = instance.as_str().unwrap();
115            
116            if let Some(min_length) = schema_obj.get("minLength") {
117                self.validate_min_length(string_val, min_length, instance_path, schema_path, errors);
118            }
119            
120            if let Some(max_length) = schema_obj.get("maxLength") {
121                self.validate_max_length(string_val, max_length, instance_path, schema_path, errors);
122            }
123            
124            if let Some(pattern) = schema_obj.get("pattern") {
125                self.validate_pattern(string_val, pattern, instance_path, schema_path, errors);
126            }
127            
128            if let Some(format) = schema_obj.get("format") {
129                self.validate_format(string_val, format, instance_path, schema_path, errors);
130            }
131        }
132
133        // Number validations
134        if instance.is_number() {
135            let num_val = instance.as_f64().unwrap();
136            
137            if let Some(minimum) = schema_obj.get("minimum") {
138                self.validate_minimum(num_val, minimum, instance_path, schema_path, errors);
139            }
140            
141            if let Some(maximum) = schema_obj.get("maximum") {
142                self.validate_maximum(num_val, maximum, instance_path, schema_path, errors);
143            }
144            
145            if let Some(multiple_of) = schema_obj.get("multipleOf") {
146                self.validate_multiple_of(num_val, multiple_of, instance_path, schema_path, errors);
147            }
148        }
149
150        // Array validations
151        if let Some(array) = instance.as_array() {
152            if let Some(min_items) = schema_obj.get("minItems") {
153                self.validate_min_items(array, min_items, instance_path, schema_path, errors);
154            }
155            
156            if let Some(max_items) = schema_obj.get("maxItems") {
157                self.validate_max_items(array, max_items, instance_path, schema_path, errors);
158            }
159            
160            if let Some(unique_items) = schema_obj.get("uniqueItems") {
161                if unique_items.as_bool().unwrap_or(false) {
162                    self.validate_unique_items(array, instance_path, schema_path, errors);
163                }
164            }
165            
166            if let Some(items_schema) = schema_obj.get("items") {
167                self.validate_array_items(array, items_schema, instance_path, schema_path, errors);
168            }
169        }
170
171        // Object validations
172        if let Some(object) = instance.as_object() {
173            if let Some(min_properties) = schema_obj.get("minProperties") {
174                self.validate_min_properties(object, min_properties, instance_path, schema_path, errors);
175            }
176            
177            if let Some(max_properties) = schema_obj.get("maxProperties") {
178                self.validate_max_properties(object, max_properties, instance_path, schema_path, errors);
179            }
180            
181            if let Some(required) = schema_obj.get("required") {
182                self.validate_required(object, required, instance_path, schema_path, errors);
183            }
184            
185            if let Some(properties) = schema_obj.get("properties") {
186                self.validate_object_properties(object, properties, instance_path, schema_path, errors);
187            }
188            
189            if let Some(additional_properties) = schema_obj.get("additionalProperties") {
190                let properties = schema_obj.get("properties");
191                self.validate_additional_properties(object, properties, additional_properties, instance_path, schema_path, errors);
192            }
193        }
194
195        // Enum validation
196        if let Some(enum_values) = schema_obj.get("enum") {
197            self.validate_enum(instance, enum_values, instance_path, schema_path, errors);
198        }
199
200        // Const validation
201        if let Some(const_value) = schema_obj.get("const") {
202            self.validate_const(instance, const_value, instance_path, schema_path, errors);
203        }
204    }
205
206    fn validate_type(
207        &self,
208        instance: &Value,
209        type_value: &Value,
210        instance_path: &str,
211        schema_path: &str,
212        errors: &mut Vec<ValidationError>,
213    ) {
214        let expected_types = match type_value {
215            Value::String(type_str) => vec![type_str.as_str()],
216            Value::Array(type_array) => {
217                type_array.iter().filter_map(|v| v.as_str()).collect()
218            }
219            _ => return,
220        };
221
222        let instance_type = get_json_type(instance);
223        let mut type_matches = false;
224        
225        for expected_type in &expected_types {
226            match expected_type {
227                &"integer" => {
228                    if let Value::Number(n) = instance {
229                        if n.is_i64() || n.is_u64() {
230                            type_matches = true;
231                            break;
232                        }
233                    }
234                }
235                &"number" => {
236                    if instance.is_number() {
237                        type_matches = true;
238                        break;
239                    }
240                }
241                _ => {
242                    if instance_type == *expected_type {
243                        type_matches = true;
244                        break;
245                    }
246                }
247            }
248        }
249        
250        if !type_matches {
251            errors.push(ValidationError {
252                instance_path: instance_path.to_string(),
253                schema_path: format!("{}/type", schema_path),
254                keyword: "type".to_string(),
255                message: format!("Expected type {}, got {}", 
256                    if expected_types.len() == 1 { 
257                        expected_types[0].to_string() 
258                    } else { 
259                        format!("one of [{}]", expected_types.join(", ")) 
260                    },
261                    instance_type
262                ),
263                instance_value: Some(instance.clone()),
264                schema_value: Some(type_value.clone()),
265            });
266        }
267    }
268
269    fn validate_min_length(
270        &self,
271        string_val: &str,
272        min_length: &Value,
273        instance_path: &str,
274        schema_path: &str,
275        errors: &mut Vec<ValidationError>,
276    ) {
277        if let Some(min_len) = min_length.as_u64() {
278            if string_val.chars().count() < min_len as usize {
279                errors.push(ValidationError {
280                    instance_path: instance_path.to_string(),
281                    schema_path: format!("{}/minLength", schema_path),
282                    keyword: "minLength".to_string(),
283                    message: format!("String length {} is less than minimum {}", 
284                        string_val.chars().count(), min_len),
285                    instance_value: Some(Value::String(string_val.to_string())),
286                    schema_value: Some(min_length.clone()),
287                });
288            }
289        }
290    }
291
292    fn validate_max_length(
293        &self,
294        string_val: &str,
295        max_length: &Value,
296        instance_path: &str,
297        schema_path: &str,
298        errors: &mut Vec<ValidationError>,
299    ) {
300        if let Some(max_len) = max_length.as_u64() {
301            if string_val.chars().count() > max_len as usize {
302                errors.push(ValidationError {
303                    instance_path: instance_path.to_string(),
304                    schema_path: format!("{}/maxLength", schema_path),
305                    keyword: "maxLength".to_string(),
306                    message: format!("String length {} exceeds maximum {}", 
307                        string_val.chars().count(), max_len),
308                    instance_value: Some(Value::String(string_val.to_string())),
309                    schema_value: Some(max_length.clone()),
310                });
311            }
312        }
313    }
314
315    fn validate_pattern(
316        &self,
317        string_val: &str,
318        pattern: &Value,
319        instance_path: &str,
320        schema_path: &str,
321        errors: &mut Vec<ValidationError>,
322    ) {
323        if let Some(pattern_str) = pattern.as_str() {
324            match Regex::new(pattern_str) {
325                Ok(regex) => {
326                    if !regex.is_match(string_val) {
327                        errors.push(ValidationError {
328                            instance_path: instance_path.to_string(),
329                            schema_path: format!("{}/pattern", schema_path),
330                            keyword: "pattern".to_string(),
331                            message: format!("String '{}' does not match pattern '{}'", 
332                                string_val, pattern_str),
333                            instance_value: Some(Value::String(string_val.to_string())),
334                            schema_value: Some(pattern.clone()),
335                        });
336                    }
337                }
338                Err(_) => {
339                    errors.push(ValidationError {
340                        instance_path: instance_path.to_string(),
341                        schema_path: format!("{}/pattern", schema_path),
342                        keyword: "pattern".to_string(),
343                        message: format!("Invalid regex pattern: '{}'", pattern_str),
344                        instance_value: Some(Value::String(string_val.to_string())),
345                        schema_value: Some(pattern.clone()),
346                    });
347                }
348            }
349        }
350    }
351
352    fn validate_format(
353        &self,
354        string_val: &str,
355        format: &Value,
356        instance_path: &str,
357        schema_path: &str,
358        errors: &mut Vec<ValidationError>,
359    ) {
360        if let Some(format_str) = format.as_str() {
361            let is_valid = match format_str {
362                "email" => validate_email(string_val),
363                "uri" => validate_uri(string_val),
364                "date" => validate_date(string_val),
365                "date-time" => validate_datetime(string_val),
366                "ipv4" => validate_ipv4(string_val),
367                "ipv6" => validate_ipv6(string_val),
368                "uuid" => validate_uuid(string_val),
369                custom_format => {
370                    if let Some(validator) = self.options.custom_formats.get(custom_format) {
371                        validator(string_val)
372                    } else {
373                        true // Unknown formats are ignored
374                    }
375                }
376            };
377
378            if !is_valid {
379                errors.push(ValidationError {
380                    instance_path: instance_path.to_string(),
381                    schema_path: format!("{}/format", schema_path),
382                    keyword: "format".to_string(),
383                    message: format!("String '{}' is not a valid {}", string_val, format_str),
384                    instance_value: Some(Value::String(string_val.to_string())),
385                    schema_value: Some(format.clone()),
386                });
387            }
388        }
389    }
390
391    fn validate_minimum(
392        &self,
393        num_val: f64,
394        minimum: &Value,
395        instance_path: &str,
396        schema_path: &str,
397        errors: &mut Vec<ValidationError>,
398    ) {
399        if let Some(min_val) = minimum.as_f64() {
400            if num_val < min_val {
401                errors.push(ValidationError {
402                    instance_path: instance_path.to_string(),
403                    schema_path: format!("{}/minimum", schema_path),
404                    keyword: "minimum".to_string(),
405                    message: format!("Value {} is less than minimum {}", num_val, min_val),
406                    instance_value: Some(Value::Number(serde_json::Number::from_f64(num_val).unwrap())),
407                    schema_value: Some(minimum.clone()),
408                });
409            }
410        }
411    }
412
413    fn validate_maximum(
414        &self,
415        num_val: f64,
416        maximum: &Value,
417        instance_path: &str,
418        schema_path: &str,
419        errors: &mut Vec<ValidationError>,
420    ) {
421        if let Some(max_val) = maximum.as_f64() {
422            if num_val > max_val {
423                errors.push(ValidationError {
424                    instance_path: instance_path.to_string(),
425                    schema_path: format!("{}/maximum", schema_path),
426                    keyword: "maximum".to_string(),
427                    message: format!("Value {} exceeds maximum {}", num_val, max_val),
428                    instance_value: Some(Value::Number(serde_json::Number::from_f64(num_val).unwrap())),
429                    schema_value: Some(maximum.clone()),
430                });
431            }
432        }
433    }
434
435    fn validate_multiple_of(
436        &self,
437        num_val: f64,
438        multiple_of: &Value,
439        instance_path: &str,
440        schema_path: &str,
441        errors: &mut Vec<ValidationError>,
442    ) {
443        if let Some(divisor) = multiple_of.as_f64() {
444            if divisor > 0.0 && (num_val / divisor).fract() != 0.0 {
445                errors.push(ValidationError {
446                    instance_path: instance_path.to_string(),
447                    schema_path: format!("{}/multipleOf", schema_path),
448                    keyword: "multipleOf".to_string(),
449                    message: format!("Value {} is not a multiple of {}", num_val, divisor),
450                    instance_value: Some(Value::Number(serde_json::Number::from_f64(num_val).unwrap())),
451                    schema_value: Some(multiple_of.clone()),
452                });
453            }
454        }
455    }
456
457    fn validate_min_items(
458        &self,
459        array: &[Value],
460        min_items: &Value,
461        instance_path: &str,
462        schema_path: &str,
463        errors: &mut Vec<ValidationError>,
464    ) {
465        if let Some(min_len) = min_items.as_u64() {
466            if array.len() < min_len as usize {
467                errors.push(ValidationError {
468                    instance_path: instance_path.to_string(),
469                    schema_path: format!("{}/minItems", schema_path),
470                    keyword: "minItems".to_string(),
471                    message: format!("Array length {} is less than minimum {}", array.len(), min_len),
472                    instance_value: Some(Value::Array(array.to_vec())),
473                    schema_value: Some(min_items.clone()),
474                });
475            }
476        }
477    }
478
479    fn validate_max_items(
480        &self,
481        array: &[Value],
482        max_items: &Value,
483        instance_path: &str,
484        schema_path: &str,
485        errors: &mut Vec<ValidationError>,
486    ) {
487        if let Some(max_len) = max_items.as_u64() {
488            if array.len() > max_len as usize {
489                errors.push(ValidationError {
490                    instance_path: instance_path.to_string(),
491                    schema_path: format!("{}/maxItems", schema_path),
492                    keyword: "maxItems".to_string(),
493                    message: format!("Array length {} exceeds maximum {}", array.len(), max_len),
494                    instance_value: Some(Value::Array(array.to_vec())),
495                    schema_value: Some(max_items.clone()),
496                });
497            }
498        }
499    }
500
501    fn validate_unique_items(
502        &self,
503        array: &[Value],
504        instance_path: &str,
505        schema_path: &str,
506        errors: &mut Vec<ValidationError>,
507    ) {
508        let mut seen = std::collections::HashSet::new();
509        for (i, item) in array.iter().enumerate() {
510            let item_str = serde_json::to_string(item).unwrap();
511            if !seen.insert(item_str) {
512                errors.push(ValidationError {
513                    instance_path: format!("{}/{}", instance_path, i),
514                    schema_path: format!("{}/uniqueItems", schema_path),
515                    keyword: "uniqueItems".to_string(),
516                    message: "Array contains duplicate items".to_string(),
517                    instance_value: Some(item.clone()),
518                    schema_value: Some(Value::Bool(true)),
519                });
520                break;
521            }
522        }
523    }
524
525    fn validate_array_items(
526        &self,
527        array: &[Value],
528        items_schema: &Value,
529        instance_path: &str,
530        schema_path: &str,
531        errors: &mut Vec<ValidationError>,
532    ) {
533        for (i, item) in array.iter().enumerate() {
534            let item_path = format!("{}/{}", instance_path, i);
535            let item_schema_path = format!("{}/items", schema_path);
536            self.validate_recursive(item, items_schema, &item_path, &item_schema_path, errors);
537        }
538    }
539
540    fn validate_min_properties(
541        &self,
542        object: &serde_json::Map<String, Value>,
543        min_properties: &Value,
544        instance_path: &str,
545        schema_path: &str,
546        errors: &mut Vec<ValidationError>,
547    ) {
548        if let Some(min_props) = min_properties.as_u64() {
549            if object.len() < min_props as usize {
550                errors.push(ValidationError {
551                    instance_path: instance_path.to_string(),
552                    schema_path: format!("{}/minProperties", schema_path),
553                    keyword: "minProperties".to_string(),
554                    message: format!("Object has {} properties, minimum is {}", object.len(), min_props),
555                    instance_value: Some(Value::Object(object.clone())),
556                    schema_value: Some(min_properties.clone()),
557                });
558            }
559        }
560    }
561
562    fn validate_max_properties(
563        &self,
564        object: &serde_json::Map<String, Value>,
565        max_properties: &Value,
566        instance_path: &str,
567        schema_path: &str,
568        errors: &mut Vec<ValidationError>,
569    ) {
570        if let Some(max_props) = max_properties.as_u64() {
571            if object.len() > max_props as usize {
572                errors.push(ValidationError {
573                    instance_path: instance_path.to_string(),
574                    schema_path: format!("{}/maxProperties", schema_path),
575                    keyword: "maxProperties".to_string(),
576                    message: format!("Object has {} properties, maximum is {}", object.len(), max_props),
577                    instance_value: Some(Value::Object(object.clone())),
578                    schema_value: Some(max_properties.clone()),
579                });
580            }
581        }
582    }
583
584    fn validate_required(
585        &self,
586        object: &serde_json::Map<String, Value>,
587        required: &Value,
588        instance_path: &str,
589        schema_path: &str,
590        errors: &mut Vec<ValidationError>,
591    ) {
592        if let Some(required_array) = required.as_array() {
593            for req_prop in required_array {
594                if let Some(prop_name) = req_prop.as_str() {
595                    if !object.contains_key(prop_name) {
596                        errors.push(ValidationError {
597                            instance_path: instance_path.to_string(),
598                            schema_path: format!("{}/required", schema_path),
599                            keyword: "required".to_string(),
600                            message: format!("Missing required property '{}'", prop_name),
601                            instance_value: Some(Value::Object(object.clone())),
602                            schema_value: Some(required.clone()),
603                        });
604                    }
605                }
606            }
607        }
608    }
609
610    fn validate_object_properties(
611        &self,
612        object: &serde_json::Map<String, Value>,
613        properties: &Value,
614        instance_path: &str,
615        schema_path: &str,
616        errors: &mut Vec<ValidationError>,
617    ) {
618        if let Some(props_obj) = properties.as_object() {
619            for (prop_name, prop_value) in object {
620                if let Some(prop_schema) = props_obj.get(prop_name) {
621                    let prop_path = format!("{}/{}", instance_path, prop_name);
622                    let prop_schema_path = format!("{}/properties/{}", schema_path, prop_name);
623                    self.validate_recursive(prop_value, prop_schema, &prop_path, &prop_schema_path, errors);
624                }
625            }
626        }
627    }
628
629    fn validate_additional_properties(
630        &self,
631        object: &serde_json::Map<String, Value>,
632        properties: Option<&Value>,
633        additional_properties: &Value,
634        instance_path: &str,
635        schema_path: &str,
636        errors: &mut Vec<ValidationError>,
637    ) {
638        let defined_props: std::collections::HashSet<&String> = properties
639            .and_then(|p| p.as_object())
640            .map(|obj| obj.keys().collect())
641            .unwrap_or_default();
642
643        for (prop_name, prop_value) in object {
644            if !defined_props.contains(prop_name) {
645                match additional_properties {
646                    Value::Bool(false) => {
647                        errors.push(ValidationError {
648                            instance_path: format!("{}/{}", instance_path, prop_name),
649                            schema_path: format!("{}/additionalProperties", schema_path),
650                            keyword: "additionalProperties".to_string(),
651                            message: format!("Additional property '{}' is not allowed", prop_name),
652                            instance_value: Some(prop_value.clone()),
653                            schema_value: Some(additional_properties.clone()),
654                        });
655                    }
656                    Value::Object(_) => {
657                        let prop_path = format!("{}/{}", instance_path, prop_name);
658                        let prop_schema_path = format!("{}/additionalProperties", schema_path);
659                        self.validate_recursive(prop_value, additional_properties, &prop_path, &prop_schema_path, errors);
660                    }
661                    _ => {} // true or non-boolean allows all additional properties
662                }
663            }
664        }
665    }
666
667    fn validate_enum(
668        &self,
669        instance: &Value,
670        enum_values: &Value,
671        instance_path: &str,
672        schema_path: &str,
673        errors: &mut Vec<ValidationError>,
674    ) {
675        if let Some(enum_array) = enum_values.as_array() {
676            if !enum_array.contains(instance) {
677                errors.push(ValidationError {
678                    instance_path: instance_path.to_string(),
679                    schema_path: format!("{}/enum", schema_path),
680                    keyword: "enum".to_string(),
681                    message: format!("Value is not one of the allowed enum values"),
682                    instance_value: Some(instance.clone()),
683                    schema_value: Some(enum_values.clone()),
684                });
685            }
686        }
687    }
688
689    fn validate_const(
690        &self,
691        instance: &Value,
692        const_value: &Value,
693        instance_path: &str,
694        schema_path: &str,
695        errors: &mut Vec<ValidationError>,
696    ) {
697        if instance != const_value {
698            errors.push(ValidationError {
699                instance_path: instance_path.to_string(),
700                schema_path: format!("{}/const", schema_path),
701                keyword: "const".to_string(),
702                message: "Value does not match the required constant".to_string(),
703                instance_value: Some(instance.clone()),
704                schema_value: Some(const_value.clone()),
705            });
706        }
707    }
708}
709
710fn get_json_type(value: &Value) -> &'static str {
711    match value {
712        Value::Null => "null",
713        Value::Bool(_) => "boolean",
714        Value::Number(_) => "number",
715        Value::String(_) => "string",
716        Value::Array(_) => "array",
717        Value::Object(_) => "object",
718    }
719}
720
721// Format validation functions
722fn validate_email(email: &str) -> bool {
723    let email_regex = Regex::new(r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$").unwrap();
724    email_regex.is_match(email)
725}
726
727fn validate_uri(uri: &str) -> bool {
728    url::Url::parse(uri).is_ok()
729}
730
731fn validate_date(date: &str) -> bool {
732    let date_regex = Regex::new(r"^\d{4}-\d{2}-\d{2}$").unwrap();
733    date_regex.is_match(date)
734}
735
736fn validate_datetime(datetime: &str) -> bool {
737    let datetime_regex = Regex::new(r"^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{3})?Z?$").unwrap();
738    datetime_regex.is_match(datetime)
739}
740
741fn validate_ipv4(ip: &str) -> bool {
742    let parts: Vec<&str> = ip.split('.').collect();
743    if parts.len() != 4 {
744        return false;
745    }
746    parts.iter().all(|part| {
747        part.parse::<u8>().is_ok()
748    })
749}
750
751fn validate_ipv6(ip: &str) -> bool {
752    let ipv6_regex = Regex::new(r"^([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$").unwrap();
753    ipv6_regex.is_match(ip)
754}
755
756fn validate_uuid(uuid: &str) -> bool {
757    let uuid_regex = Regex::new(r"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$").unwrap();
758    uuid_regex.is_match(uuid)
759}
760
761// C FFI exports
762#[no_mangle]
763pub extern "C" fn validate_json_simple(
764    schema_json: *const c_char,
765    instance_json: *const c_char,
766) -> *mut c_char {
767    if schema_json.is_null() || instance_json.is_null() {
768        return std::ptr::null_mut();
769    }
770
771    let schema_str = unsafe { CStr::from_ptr(schema_json) };
772    let instance_str = unsafe { CStr::from_ptr(instance_json) };
773
774    let schema_str = match schema_str.to_str() {
775        Ok(s) => s,
776        Err(_) => return std::ptr::null_mut(),
777    };
778
779    let instance_str = match instance_str.to_str() {
780        Ok(s) => s,
781        Err(_) => return std::ptr::null_mut(),
782    };
783
784    let schema: Value = match serde_json::from_str(schema_str) {
785        Ok(s) => s,
786        Err(_) => return std::ptr::null_mut(),
787    };
788
789    let instance: Value = match serde_json::from_str(instance_str) {
790        Ok(i) => i,
791        Err(_) => return std::ptr::null_mut(),
792    };
793
794    let options = ValidationOptions::default();
795    let validator = match JsonSchemaValidator::new(schema, options) {
796        Ok(v) => v,
797        Err(_) => return std::ptr::null_mut(),
798    };
799
800    let errors = validator.validate(&instance);
801    let result = serde_json::to_string(&errors).unwrap_or_else(|_| "[]".to_string());
802
803    match CString::new(result) {
804        Ok(c_string) => c_string.into_raw(),
805        Err(_) => std::ptr::null_mut(),
806    }
807}
808
809#[no_mangle]
810pub extern "C" fn free_string(ptr: *mut c_char) {
811    if !ptr.is_null() {
812        unsafe {
813            let _ = CString::from_raw(ptr);
814        }
815    }
816}
817
818// WebAssembly exports
819#[wasm_bindgen]
820pub fn wasm_validate_json(schema_json: &str, instance_json: &str) -> String {
821    let schema: Value = match serde_json::from_str(schema_json) {
822        Ok(s) => s,
823        Err(e) => return format!("{{\"error\": \"Invalid schema JSON: {}\"}}", e),
824    };
825
826    let instance: Value = match serde_json::from_str(instance_json) {
827        Ok(i) => i,
828        Err(e) => return format!("{{\"error\": \"Invalid instance JSON: {}\"}}", e),
829    };
830
831    let options = ValidationOptions::default();
832    let validator = match JsonSchemaValidator::new(schema, options) {
833        Ok(v) => v,
834        Err(e) => return serde_json::to_string(&e).unwrap_or_else(|_| "{}".to_string()),
835    };
836
837    let errors = validator.validate(&instance);
838    serde_json::to_string(&errors).unwrap_or_else(|_| "[]".to_string())
839}
840
841#[wasm_bindgen]
842pub fn wasm_is_valid(schema_json: &str, instance_json: &str) -> bool {
843    let schema: Value = match serde_json::from_str(schema_json) {
844        Ok(s) => s,
845        Err(_) => return false,
846    };
847
848    let instance: Value = match serde_json::from_str(instance_json) {
849        Ok(i) => i,
850        Err(_) => return false,
851    };
852
853    let options = ValidationOptions::default();
854    let validator = match JsonSchemaValidator::new(schema, options) {
855        Ok(v) => v,
856        Err(_) => return false,
857    };
858
859    validator.is_valid(&instance)
860}
861
862#[cfg(test)]
863mod tests {
864    use super::*;
865
866    #[test]
867    fn test_basic_validation() {
868        let schema = serde_json::json!({
869            "type": "object",
870            "properties": {
871                "name": {"type": "string"},
872                "age": {"type": "integer", "minimum": 0}
873            },
874            "required": ["name"]
875        });
876
877        let valid_instance = serde_json::json!({
878            "name": "John",
879            "age": 30
880        });
881
882        let invalid_instance = serde_json::json!({
883            "age": -5
884        });
885
886        let options = ValidationOptions::default();
887        let validator = JsonSchemaValidator::new(schema, options).unwrap();
888
889        assert!(validator.is_valid(&valid_instance));
890        assert!(!validator.is_valid(&invalid_instance));
891
892        let errors = validator.validate(&invalid_instance);
893        assert_eq!(errors.len(), 2); // Missing name + negative age
894    }
895
896    #[test]
897    fn test_string_validations() {
898        let schema = serde_json::json!({
899            "type": "string",
900            "minLength": 3,
901            "maxLength": 10,
902            "pattern": "^[a-z]+$"
903        });
904
905        let options = ValidationOptions::default();
906        let validator = JsonSchemaValidator::new(schema, options).unwrap();
907
908        assert!(validator.is_valid(&serde_json::json!("hello")));
909        assert!(!validator.is_valid(&serde_json::json!("hi"))); // too short
910        assert!(!validator.is_valid(&serde_json::json!("verylongstring"))); // too long
911        assert!(!validator.is_valid(&serde_json::json!("Hello"))); // uppercase
912    }
913
914    #[test]
915    fn test_array_validations() {
916        let schema = serde_json::json!({
917            "type": "array",
918            "minItems": 1,
919            "maxItems": 3,
920            "uniqueItems": true,
921            "items": {"type": "number"}
922        });
923
924        let options = ValidationOptions::default();
925        let validator = JsonSchemaValidator::new(schema, options).unwrap();
926
927        assert!(validator.is_valid(&serde_json::json!([1, 2, 3])));
928        assert!(!validator.is_valid(&serde_json::json!([]))); // too few items
929        assert!(!validator.is_valid(&serde_json::json!([1, 2, 3, 4]))); // too many items
930        assert!(!validator.is_valid(&serde_json::json!([1, 1, 2]))); // not unique
931        assert!(!validator.is_valid(&serde_json::json!([1, "2", 3]))); // wrong type
932    }
933
934    #[test]
935    fn test_format_validation() {
936        let schema = serde_json::json!({
937            "type": "string",
938            "format": "email"
939        });
940
941        let options = ValidationOptions::default();
942        let validator = JsonSchemaValidator::new(schema, options).unwrap();
943
944        assert!(validator.is_valid(&serde_json::json!("user@example.com")));
945        assert!(!validator.is_valid(&serde_json::json!("invalid-email")));
946    }
947
948    #[test]
949    fn test_enum_validation() {
950        let schema = serde_json::json!({
951            "enum": ["red", "green", "blue"]
952        });
953
954        let options = ValidationOptions::default();
955        let validator = JsonSchemaValidator::new(schema, options).unwrap();
956
957        assert!(validator.is_valid(&serde_json::json!("red")));
958        assert!(!validator.is_valid(&serde_json::json!("yellow")));
959    }
960}