1use serde::Serialize;
29use std::collections::HashMap;
30use wasm_bindgen::prelude::*;
31
32pub use domainstack::{Validate, ValidationError};
34pub use serde::de::DeserializeOwned;
35
36#[wasm_bindgen(start)]
39pub fn init() {
40 #[cfg(feature = "console_error_panic_hook")]
41 console_error_panic_hook::set_once();
42}
43
44#[derive(Debug, Clone, Serialize)]
52pub struct ValidationResult {
53 pub ok: bool,
55
56 #[serde(skip_serializing_if = "Option::is_none")]
58 pub errors: Option<Vec<WasmViolation>>,
59
60 #[serde(skip_serializing_if = "Option::is_none")]
62 pub error: Option<SystemError>,
63}
64
65impl ValidationResult {
66 pub fn success() -> Self {
68 Self {
69 ok: true,
70 errors: None,
71 error: None,
72 }
73 }
74
75 pub fn validation_failed(violations: Vec<WasmViolation>) -> Self {
77 Self {
78 ok: false,
79 errors: Some(violations),
80 error: None,
81 }
82 }
83
84 pub fn system_error(code: &'static str, message: String) -> Self {
86 Self {
87 ok: false,
88 errors: None,
89 error: Some(SystemError { code, message }),
90 }
91 }
92}
93
94#[derive(Debug, Clone, Serialize)]
96pub struct WasmViolation {
97 pub path: String,
99
100 pub code: String,
102
103 pub message: String,
105
106 #[serde(skip_serializing_if = "Option::is_none")]
108 pub meta: Option<HashMap<String, String>>,
109}
110
111impl From<&domainstack::Violation> for WasmViolation {
112 fn from(v: &domainstack::Violation) -> Self {
113 let meta = if v.meta.is_empty() {
114 None
115 } else {
116 Some(
117 v.meta
118 .iter()
119 .map(|(k, v)| (k.to_string(), v.to_string()))
120 .collect(),
121 )
122 };
123
124 Self {
125 path: v.path.to_string(),
126 code: v.code.to_string(),
127 message: v.message.clone(),
128 meta,
129 }
130 }
131}
132
133#[derive(Debug, Clone, Serialize)]
135pub struct SystemError {
136 pub code: &'static str,
138
139 pub message: String,
141}
142
143pub enum DispatchError {
149 UnknownType,
151
152 ParseError(String),
154
155 Validation(Box<ValidationError>),
157}
158
159pub type ValidateFn = fn(&str) -> Result<(), DispatchError>;
165
166pub struct TypeRegistry {
171 validators: HashMap<&'static str, ValidateFn>,
172}
173
174impl TypeRegistry {
175 pub fn new() -> Self {
177 Self {
178 validators: HashMap::new(),
179 }
180 }
181
182 pub fn register<T>(&mut self, type_name: &'static str)
184 where
185 T: Validate + DeserializeOwned + 'static,
186 {
187 self.validators.insert(type_name, validate_json::<T>);
188 }
189
190 pub fn validate(&self, type_name: &str, json: &str) -> Result<(), DispatchError> {
192 match self.validators.get(type_name) {
193 Some(validate_fn) => validate_fn(json),
194 None => Err(DispatchError::UnknownType),
195 }
196 }
197
198 pub fn has_type(&self, type_name: &str) -> bool {
200 self.validators.contains_key(type_name)
201 }
202
203 pub fn type_names(&self) -> Vec<&'static str> {
205 self.validators.keys().copied().collect()
206 }
207}
208
209impl Default for TypeRegistry {
210 fn default() -> Self {
211 Self::new()
212 }
213}
214
215fn validate_json<T>(json: &str) -> Result<(), DispatchError>
217where
218 T: Validate + DeserializeOwned,
219{
220 let value: T =
221 serde_json::from_str(json).map_err(|e| DispatchError::ParseError(e.to_string()))?;
222 value
223 .validate()
224 .map_err(|e| DispatchError::Validation(Box::new(e)))
225}
226
227use std::cell::RefCell;
232
233thread_local! {
234 static REGISTRY: RefCell<TypeRegistry> = RefCell::new(TypeRegistry::new());
235}
236
237pub fn register_type<T>(type_name: &'static str)
255where
256 T: Validate + DeserializeOwned + 'static,
257{
258 REGISTRY.with(|r| r.borrow_mut().register::<T>(type_name));
259}
260
261pub fn is_type_registered(type_name: &str) -> bool {
263 REGISTRY.with(|r| r.borrow().has_type(type_name))
264}
265
266pub fn registered_types() -> Vec<&'static str> {
268 REGISTRY.with(|r| r.borrow().type_names())
269}
270
271#[wasm_bindgen]
289pub fn validate(type_name: &str, json: &str) -> JsValue {
290 let result = REGISTRY.with(|r| r.borrow().validate(type_name, json));
291
292 let validation_result = match result {
293 Ok(()) => ValidationResult::success(),
294 Err(DispatchError::UnknownType) => {
295 ValidationResult::system_error("unknown_type", format!("Unknown type: {}", type_name))
296 }
297 Err(DispatchError::ParseError(msg)) => ValidationResult::system_error("parse_error", msg),
298 Err(DispatchError::Validation(err)) => {
299 let violations = err.violations.iter().map(WasmViolation::from).collect();
300 ValidationResult::validation_failed(violations)
301 }
302 };
303
304 serde_wasm_bindgen::to_value(&validation_result).unwrap_or_else(|_| {
306 let fallback = ValidationResult::system_error(
307 "internal_error",
308 "Failed to serialize validation result".to_string(),
309 );
310 serde_wasm_bindgen::to_value(&fallback).unwrap()
311 })
312}
313
314#[wasm_bindgen]
324pub fn validate_object(type_name: &str, value: JsValue) -> JsValue {
325 let json = match js_sys::JSON::stringify(&value) {
327 Ok(s) => s.as_string().unwrap_or_default(),
328 Err(_) => {
329 let result = ValidationResult::system_error(
330 "parse_error",
331 "Failed to serialize JavaScript object to JSON".to_string(),
332 );
333 return serde_wasm_bindgen::to_value(&result).unwrap();
334 }
335 };
336
337 validate(type_name, &json)
338}
339
340#[wasm_bindgen]
344pub fn get_registered_types() -> JsValue {
345 let types = registered_types();
346 serde_wasm_bindgen::to_value(&types).unwrap_or(JsValue::NULL)
347}
348
349#[wasm_bindgen]
351pub fn has_type(type_name: &str) -> bool {
352 is_type_registered(type_name)
353}
354
355#[wasm_bindgen]
366pub struct Validator {
367 _private: (),
369}
370
371#[wasm_bindgen]
372impl Validator {
373 pub fn validate(&self, type_name: &str, json: &str) -> JsValue {
375 validate(type_name, json)
376 }
377
378 #[wasm_bindgen(js_name = validateObject)]
380 pub fn validate_object(&self, type_name: &str, value: JsValue) -> JsValue {
381 validate_object(type_name, value)
382 }
383
384 #[wasm_bindgen(js_name = getTypes)]
386 pub fn get_types(&self) -> JsValue {
387 get_registered_types()
388 }
389
390 #[wasm_bindgen(js_name = hasType)]
392 pub fn has_type(&self, type_name: &str) -> bool {
393 has_type(type_name)
394 }
395}
396
397#[wasm_bindgen(js_name = createValidator)]
407pub fn create_validator() -> Validator {
408 Validator { _private: () }
409}
410
411#[cfg(test)]
416mod tests {
417 use super::*;
418
419 #[test]
420 fn test_validation_result_success() {
421 let result = ValidationResult::success();
422 assert!(result.ok);
423 assert!(result.errors.is_none());
424 assert!(result.error.is_none());
425 }
426
427 #[test]
428 fn test_validation_result_failure() {
429 let violations = vec![WasmViolation {
430 path: "email".to_string(),
431 code: "invalid_email".to_string(),
432 message: "Invalid email format".to_string(),
433 meta: None,
434 }];
435 let result = ValidationResult::validation_failed(violations);
436 assert!(!result.ok);
437 assert!(result.errors.is_some());
438 assert_eq!(result.errors.as_ref().unwrap().len(), 1);
439 assert!(result.error.is_none());
440 }
441
442 #[test]
443 fn test_validation_result_system_error() {
444 let result =
445 ValidationResult::system_error("unknown_type", "Unknown type: Foo".to_string());
446 assert!(!result.ok);
447 assert!(result.errors.is_none());
448 assert!(result.error.is_some());
449 assert_eq!(result.error.as_ref().unwrap().code, "unknown_type");
450 }
451
452 #[test]
453 fn test_registry_unknown_type() {
454 let registry = TypeRegistry::new();
455 let result = registry.validate("NonExistent", "{}");
456 assert!(matches!(result, Err(DispatchError::UnknownType)));
457 }
458
459 mod integration {
461 use super::*;
462 use domainstack::Validate;
463 use serde::Deserialize;
464
465 #[derive(Debug, Validate, Deserialize)]
466 struct TestBooking {
467 #[validate(length(min = 1, max = 100))]
468 guest_name: String,
469
470 #[validate(range(min = 1, max = 10))]
471 rooms: u8,
472 }
473
474 #[test]
475 fn test_register_and_validate_success() {
476 let mut registry = TypeRegistry::new();
477 registry.register::<TestBooking>("TestBooking");
478
479 let json = r#"{"guest_name": "John Doe", "rooms": 2}"#;
480 let result = registry.validate("TestBooking", json);
481 assert!(result.is_ok());
482 }
483
484 #[test]
485 fn test_register_and_validate_failure() {
486 let mut registry = TypeRegistry::new();
487 registry.register::<TestBooking>("TestBooking");
488
489 let json = r#"{"guest_name": "John", "rooms": 15}"#;
491 let result = registry.validate("TestBooking", json);
492
493 assert!(matches!(result, Err(DispatchError::Validation(_))));
494 if let Err(DispatchError::Validation(err)) = result {
495 assert!(!err.violations.is_empty());
496 assert_eq!(err.violations[0].path.to_string(), "rooms");
497 }
498 }
499
500 #[test]
501 fn test_parse_error() {
502 let mut registry = TypeRegistry::new();
503 registry.register::<TestBooking>("TestBooking");
504
505 let json = r#"{"guest_name": "John", "rooms": "not a number"}"#;
506 let result = registry.validate("TestBooking", json);
507
508 assert!(matches!(result, Err(DispatchError::ParseError(_))));
509 }
510
511 #[test]
512 fn test_wasm_violation_from_violation() {
513 let violation = domainstack::Violation {
514 path: domainstack::Path::from("email"),
515 code: "invalid_email",
516 message: "Invalid email format".to_string(),
517 meta: domainstack::Meta::default(),
518 };
519
520 let wasm_violation = WasmViolation::from(&violation);
521 assert_eq!(wasm_violation.path, "email");
522 assert_eq!(wasm_violation.code, "invalid_email");
523 assert_eq!(wasm_violation.message, "Invalid email format");
524 assert!(wasm_violation.meta.is_none());
525 }
526
527 #[test]
528 fn test_wasm_violation_with_meta() {
529 let mut meta = domainstack::Meta::default();
530 meta.insert("min", "1");
531 meta.insert("max", "10");
532
533 let violation = domainstack::Violation {
534 path: domainstack::Path::from("age"),
535 code: "out_of_range",
536 message: "Must be between 1 and 10".to_string(),
537 meta,
538 };
539
540 let wasm_violation = WasmViolation::from(&violation);
541 assert!(wasm_violation.meta.is_some());
542 let meta = wasm_violation.meta.unwrap();
543 assert_eq!(meta.get("min"), Some(&"1".to_string()));
544 assert_eq!(meta.get("max"), Some(&"10".to_string()));
545 }
546
547 #[test]
548 fn test_multiple_violations() {
549 let mut registry = TypeRegistry::new();
550 registry.register::<TestBooking>("TestBooking");
551
552 let json = r#"{"guest_name": "", "rooms": 15}"#;
554 let result = registry.validate("TestBooking", json);
555
556 assert!(matches!(result, Err(DispatchError::Validation(_))));
557 if let Err(DispatchError::Validation(err)) = result {
558 assert_eq!(err.violations.len(), 2);
559 }
560 }
561
562 #[test]
563 fn test_empty_json_object() {
564 let mut registry = TypeRegistry::new();
565 registry.register::<TestBooking>("TestBooking");
566
567 let json = r#"{}"#;
569 let result = registry.validate("TestBooking", json);
570 assert!(matches!(result, Err(DispatchError::ParseError(_))));
571 }
572
573 #[test]
574 fn test_invalid_json_syntax() {
575 let mut registry = TypeRegistry::new();
576 registry.register::<TestBooking>("TestBooking");
577
578 let json = r#"{ invalid json }"#;
579 let result = registry.validate("TestBooking", json);
580 assert!(matches!(result, Err(DispatchError::ParseError(_))));
581 }
582
583 #[test]
584 fn test_registry_has_type() {
585 let mut registry = TypeRegistry::new();
586 assert!(!registry.has_type("TestBooking"));
587
588 registry.register::<TestBooking>("TestBooking");
589 assert!(registry.has_type("TestBooking"));
590 assert!(!registry.has_type("NonExistent"));
591 }
592
593 #[test]
594 fn test_registry_type_names() {
595 let mut registry = TypeRegistry::new();
596 assert!(registry.type_names().is_empty());
597
598 registry.register::<TestBooking>("TestBooking");
599 let names = registry.type_names();
600 assert_eq!(names.len(), 1);
601 assert!(names.contains(&"TestBooking"));
602 }
603
604 #[derive(Debug, Validate, Deserialize)]
606 struct TestAddress {
607 #[validate(length(min = 1, max = 100))]
608 street: String,
609
610 #[validate(length(min = 1, max = 50))]
611 city: String,
612 }
613
614 #[derive(Debug, Validate, Deserialize)]
615 struct TestPerson {
616 #[validate(length(min = 1, max = 50))]
617 name: String,
618
619 #[validate(nested)]
620 address: TestAddress,
621 }
622
623 #[test]
624 fn test_nested_validation_with_path() {
625 let mut registry = TypeRegistry::new();
626 registry.register::<TestPerson>("TestPerson");
627
628 let json = r#"{"name": "John", "address": {"street": "123 Main", "city": ""}}"#;
630 let result = registry.validate("TestPerson", json);
631
632 assert!(matches!(result, Err(DispatchError::Validation(_))));
633 if let Err(DispatchError::Validation(err)) = result {
634 assert!(!err.violations.is_empty());
635 let path = err.violations[0].path.to_string();
637 assert!(path.contains("address") || path.contains("city"));
638 }
639 }
640 }
641
642 mod global_registry {
644 use super::*;
645 use serde::Deserialize;
646
647 #[derive(Debug, Validate, Deserialize)]
648 struct GlobalTestType {
649 #[validate(length(min = 1))]
650 name: String,
651 }
652
653 #[test]
654 fn test_global_register_and_check() {
655 register_type::<GlobalTestType>("GlobalTestType");
657
658 assert!(is_type_registered("GlobalTestType"));
660 assert!(!is_type_registered("NotRegistered"));
661
662 let types = registered_types();
664 assert!(types.contains(&"GlobalTestType"));
665 }
666 }
667
668 mod validation_result {
670 use super::*;
671
672 #[test]
673 fn test_success_serialization() {
674 let result = ValidationResult::success();
675 let json = serde_json::to_string(&result).unwrap();
676 assert!(json.contains("\"ok\":true"));
677 assert!(!json.contains("errors"));
678 assert!(!json.contains("error"));
679 }
680
681 #[test]
682 fn test_validation_failed_serialization() {
683 let violations = vec![
684 WasmViolation {
685 path: "field1".to_string(),
686 code: "error1".to_string(),
687 message: "Error 1".to_string(),
688 meta: None,
689 },
690 WasmViolation {
691 path: "field2".to_string(),
692 code: "error2".to_string(),
693 message: "Error 2".to_string(),
694 meta: None,
695 },
696 ];
697 let result = ValidationResult::validation_failed(violations);
698 let json = serde_json::to_string(&result).unwrap();
699 assert!(json.contains("\"ok\":false"));
700 assert!(json.contains("\"errors\""));
701 assert!(json.contains("field1"));
702 assert!(json.contains("field2"));
703 }
704
705 #[test]
706 fn test_system_error_serialization() {
707 let result = ValidationResult::system_error("parse_error", "Invalid JSON".to_string());
708 let json = serde_json::to_string(&result).unwrap();
709 assert!(json.contains("\"ok\":false"));
710 assert!(json.contains("\"error\""));
711 assert!(json.contains("parse_error"));
712 assert!(json.contains("Invalid JSON"));
713 }
714
715 #[test]
716 fn test_empty_violations_list() {
717 let result = ValidationResult::validation_failed(vec![]);
718 let json = serde_json::to_string(&result).unwrap();
719 assert!(json.contains("\"ok\":false"));
720 assert!(json.contains("\"errors\":[]"));
721 }
722
723 #[test]
724 fn test_violation_with_meta_serialization() {
725 let mut meta = HashMap::new();
726 meta.insert("min".to_string(), "1".to_string());
727 meta.insert("max".to_string(), "10".to_string());
728
729 let violations = vec![WasmViolation {
730 path: "count".to_string(),
731 code: "out_of_range".to_string(),
732 message: "Must be between 1 and 10".to_string(),
733 meta: Some(meta),
734 }];
735 let result = ValidationResult::validation_failed(violations);
736 let json = serde_json::to_string(&result).unwrap();
737 assert!(json.contains("\"meta\""));
738 assert!(json.contains("\"min\":\"1\""));
739 assert!(json.contains("\"max\":\"10\""));
740 }
741
742 #[test]
743 fn test_special_characters_in_message() {
744 let result = ValidationResult::system_error(
745 "parse_error",
746 r#"Expected "}" at line 1, column 5"#.to_string(),
747 );
748 let json = serde_json::to_string(&result).unwrap();
749 assert!(json.contains("parse_error"));
750 assert!(serde_json::from_str::<serde_json::Value>(&json).is_ok());
752 }
753 }
754
755 mod registry_edge_cases {
757 use super::*;
758 use serde::Deserialize;
759
760 #[derive(Debug, Validate, Deserialize)]
761 struct TypeA {
762 #[validate(length(min = 1))]
763 name: String,
764 }
765
766 #[derive(Debug, Validate, Deserialize)]
767 struct TypeB {
768 #[validate(range(min = 0, max = 100))]
769 value: i32,
770 }
771
772 #[test]
773 fn test_registry_default_impl() {
774 let registry = TypeRegistry::default();
775 assert!(registry.type_names().is_empty());
776 }
777
778 #[test]
779 fn test_multiple_types_registration() {
780 let mut registry = TypeRegistry::new();
781 registry.register::<TypeA>("TypeA");
782 registry.register::<TypeB>("TypeB");
783
784 assert!(registry.has_type("TypeA"));
785 assert!(registry.has_type("TypeB"));
786 assert_eq!(registry.type_names().len(), 2);
787 }
788
789 #[test]
790 fn test_type_overwriting() {
791 let mut registry = TypeRegistry::new();
792 registry.register::<TypeA>("SharedName");
793
794 let result = registry.validate("SharedName", r#"{"name": ""}"#);
796 assert!(matches!(result, Err(DispatchError::Validation(_))));
797
798 registry.register::<TypeB>("SharedName");
800
801 let result = registry.validate("SharedName", r#"{"value": 50}"#);
803 assert!(result.is_ok());
804 }
805
806 #[test]
807 fn test_empty_type_name() {
808 let mut registry = TypeRegistry::new();
809 registry.register::<TypeA>("");
810
811 assert!(registry.has_type(""));
812 let result = registry.validate("", r#"{"name": "test"}"#);
813 assert!(result.is_ok());
814 }
815
816 #[test]
817 fn test_type_name_with_special_characters() {
818 let mut registry = TypeRegistry::new();
819 registry.register::<TypeA>("Type::With::Colons");
820 registry.register::<TypeB>("Type<Generic>");
821
822 assert!(registry.has_type("Type::With::Colons"));
823 assert!(registry.has_type("Type<Generic>"));
824 }
825
826 #[test]
827 fn test_validate_with_whitespace_in_json() {
828 let mut registry = TypeRegistry::new();
829 registry.register::<TypeA>("TypeA");
830
831 let json = r#"
832 {
833 "name": "test"
834 }
835 "#;
836 let result = registry.validate("TypeA", json);
837 assert!(result.is_ok());
838 }
839
840 #[test]
841 fn test_validate_with_extra_fields_in_json() {
842 let mut registry = TypeRegistry::new();
843 registry.register::<TypeA>("TypeA");
844
845 let json = r#"{"name": "test", "extra": "ignored"}"#;
847 let result = registry.validate("TypeA", json);
848 assert!(result.is_ok());
849 }
850
851 #[test]
852 fn test_unicode_in_validation_values() {
853 let mut registry = TypeRegistry::new();
854 registry.register::<TypeA>("TypeA");
855
856 let json = r#"{"name": "日本語テスト 🎉"}"#;
857 let result = registry.validate("TypeA", json);
858 assert!(result.is_ok());
859 }
860
861 #[test]
862 fn test_null_value_in_json() {
863 let mut registry = TypeRegistry::new();
864 registry.register::<TypeA>("TypeA");
865
866 let json = r#"{"name": null}"#;
867 let result = registry.validate("TypeA", json);
868 assert!(matches!(result, Err(DispatchError::ParseError(_))));
870 }
871
872 #[test]
873 fn test_empty_string_validation() {
874 let mut registry = TypeRegistry::new();
875 registry.register::<TypeA>("TypeA");
876
877 let json = r#"{"name": ""}"#;
878 let result = registry.validate("TypeA", json);
879 assert!(matches!(result, Err(DispatchError::Validation(_))));
881 }
882 }
883
884 mod wasm_violation_edge_cases {
886 use super::*;
887
888 #[test]
889 fn test_violation_with_empty_path() {
890 let violation = domainstack::Violation {
891 path: domainstack::Path::root(),
892 code: "invalid",
893 message: "Invalid".to_string(),
894 meta: domainstack::Meta::default(),
895 };
896
897 let wasm_violation = WasmViolation::from(&violation);
898 assert_eq!(wasm_violation.path, "");
899 }
900
901 #[test]
902 fn test_violation_with_complex_nested_path() {
903 let path = domainstack::Path::root()
904 .field("orders")
905 .index(0)
906 .field("items")
907 .index(5)
908 .field("variant");
909
910 let violation = domainstack::Violation {
911 path,
912 code: "invalid",
913 message: "Invalid".to_string(),
914 meta: domainstack::Meta::default(),
915 };
916
917 let wasm_violation = WasmViolation::from(&violation);
918 assert_eq!(wasm_violation.path, "orders[0].items[5].variant");
919 }
920
921 #[test]
922 fn test_violation_with_special_chars_in_code() {
923 let violation = domainstack::Violation {
924 path: domainstack::Path::from("field"),
925 code: "error_code_with_underscores",
926 message: "Error".to_string(),
927 meta: domainstack::Meta::default(),
928 };
929
930 let wasm_violation = WasmViolation::from(&violation);
931 assert_eq!(wasm_violation.code, "error_code_with_underscores");
932 }
933
934 #[test]
935 fn test_violation_preserves_long_message() {
936 let long_message = "A".repeat(1000);
937 let violation = domainstack::Violation {
938 path: domainstack::Path::from("field"),
939 code: "error",
940 message: long_message.clone(),
941 meta: domainstack::Meta::default(),
942 };
943
944 let wasm_violation = WasmViolation::from(&violation);
945 assert_eq!(wasm_violation.message, long_message);
946 }
947
948 #[test]
949 fn test_violation_meta_numeric_values() {
950 let mut meta = domainstack::Meta::default();
951 meta.insert("min", 1);
952 meta.insert("max", 100);
953 meta.insert("actual", 150);
954
955 let violation = domainstack::Violation {
956 path: domainstack::Path::from("field"),
957 code: "out_of_range",
958 message: "Out of range".to_string(),
959 meta,
960 };
961
962 let wasm_violation = WasmViolation::from(&violation);
963 let meta = wasm_violation.meta.unwrap();
964 assert_eq!(meta.get("min"), Some(&"1".to_string()));
966 assert_eq!(meta.get("max"), Some(&"100".to_string()));
967 assert_eq!(meta.get("actual"), Some(&"150".to_string()));
968 }
969 }
970
971 mod dispatch_error_tests {
973 use super::*;
974 use serde::Deserialize;
975
976 #[derive(Debug, Validate, Deserialize)]
977 #[allow(dead_code)]
978 struct SimpleType {
979 value: i32,
980 }
981
982 #[test]
983 fn test_dispatch_error_unknown_type_message() {
984 let registry = TypeRegistry::new();
985 let result = registry.validate("DoesNotExist", "{}");
986
987 match result {
988 Err(DispatchError::UnknownType) => {}
989 _ => panic!("Expected UnknownType error"),
990 }
991 }
992
993 #[test]
994 fn test_dispatch_error_parse_error_contains_details() {
995 let mut registry = TypeRegistry::new();
996 registry.register::<SimpleType>("SimpleType");
997
998 let result = registry.validate("SimpleType", r#"{"value": "not_a_number"}"#);
999
1000 match result {
1001 Err(DispatchError::ParseError(msg)) => {
1002 assert!(!msg.is_empty());
1003 }
1004 _ => panic!("Expected ParseError"),
1005 }
1006 }
1007
1008 #[test]
1009 fn test_dispatch_error_validation_boxed() {
1010 let mut registry = TypeRegistry::new();
1011
1012 #[derive(Debug, Validate, Deserialize)]
1013 struct AlwaysInvalid {
1014 #[validate(range(min = 10, max = 5))] value: i32,
1016 }
1017
1018 registry.register::<AlwaysInvalid>("AlwaysInvalid");
1019
1020 let result = registry.validate("AlwaysInvalid", r#"{"value": 7}"#);
1022
1023 match result {
1024 Err(DispatchError::Validation(boxed_err)) => {
1025 assert!(!boxed_err.violations.is_empty());
1026 }
1027 _ => panic!("Expected Validation error"),
1028 }
1029 }
1030 }
1031}