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 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 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 if let Some(type_value) = schema_obj.get("type") {
109 self.validate_type(instance, type_value, instance_path, schema_path, errors);
110 }
111
112 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 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 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 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 if let Some(enum_values) = schema_obj.get("enum") {
197 self.validate_enum(instance, enum_values, instance_path, schema_path, errors);
198 }
199
200 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 }
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 _ => {} }
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
721fn 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#[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#[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); }
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"))); assert!(!validator.is_valid(&serde_json::json!("verylongstring"))); assert!(!validator.is_valid(&serde_json::json!("Hello"))); }
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!([]))); assert!(!validator.is_valid(&serde_json::json!([1, 2, 3, 4]))); assert!(!validator.is_valid(&serde_json::json!([1, 1, 2]))); assert!(!validator.is_valid(&serde_json::json!([1, "2", 3]))); }
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}