gear_mesh_core/
validation.rs1use serde::{Deserialize, Serialize};
6
7#[derive(Debug, Clone, Serialize, Deserialize)]
9pub enum ValidationRule {
10 Range { min: Option<f64>, max: Option<f64> },
12 Length {
14 min: Option<usize>,
15 max: Option<usize>,
16 },
17 Pattern(String),
19 Email,
21 Url,
23 Required,
25 Custom {
27 name: String,
28 message: Option<String>,
29 },
30}
31
32impl ValidationRule {
33 pub fn to_typescript_check(&self, field_name: &str) -> String {
35 match self {
36 ValidationRule::Range { min, max } => {
37 let mut checks = Vec::new();
38 if let Some(min) = min {
39 checks.push(format!("obj.{field_name} >= {min}"));
40 }
41 if let Some(max) = max {
42 checks.push(format!("obj.{field_name} <= {max}"));
43 }
44 if checks.is_empty() {
45 "true".to_string()
46 } else {
47 checks.join(" && ")
48 }
49 }
50 ValidationRule::Length { min, max } => {
51 let mut checks = Vec::new();
52 if let Some(min) = min {
53 checks.push(format!("obj.{field_name}.length >= {min}"));
54 }
55 if let Some(max) = max {
56 checks.push(format!("obj.{field_name}.length <= {max}"));
57 }
58 if checks.is_empty() {
59 "true".to_string()
60 } else {
61 checks.join(" && ")
62 }
63 }
64 ValidationRule::Pattern(pattern) => {
65 format!("/{pattern}/.test(obj.{field_name})")
66 }
67 ValidationRule::Email => {
68 format!(r#"/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(obj.{field_name})"#)
69 }
70 ValidationRule::Url => {
71 format!(r#"/^https?:\/\/[^\s]+$/.test(obj.{field_name})"#)
72 }
73 ValidationRule::Required => {
74 format!("obj.{field_name} !== undefined && obj.{field_name} !== null")
75 }
76 ValidationRule::Custom { name, .. } => {
77 format!("validate{name}(obj.{field_name})")
78 }
79 }
80 }
81
82 pub fn to_zod_schema(&self, is_bigint: bool) -> String {
84 match self {
85 ValidationRule::Range { min, max } => {
86 let mut schema = String::new();
87 let suffix = if is_bigint { "n" } else { "" };
88 if let Some(min) = min {
89 if is_bigint {
90 debug_assert!(
91 min.fract() == 0.0,
92 "Fractional value ({}) provided for integer range validation, this will be truncated to {}",
93 min,
94 *min as i128
95 );
96 schema.push_str(&format!(".min({}{suffix})", *min as i128));
97 } else {
98 schema.push_str(&format!(".min({min})"));
99 }
100 }
101 if let Some(max) = max {
102 if is_bigint {
103 debug_assert!(
104 max.fract() == 0.0,
105 "Fractional value ({}) provided for integer range validation, this will be truncated to {}",
106 max,
107 *max as i128
108 );
109 schema.push_str(&format!(".max({}{suffix})", *max as i128));
110 } else {
111 schema.push_str(&format!(".max({max})"));
112 }
113 }
114 schema
115 }
116 ValidationRule::Length { min, max } => {
117 let mut schema = String::new();
118 if let Some(min) = min {
119 schema.push_str(&format!(".min({min})"));
120 }
121 if let Some(max) = max {
122 schema.push_str(&format!(".max({max})"));
123 }
124 schema
125 }
126 ValidationRule::Pattern(pattern) => {
127 format!(".regex(/{pattern}/)")
128 }
129 ValidationRule::Email => ".email()".to_string(),
130 ValidationRule::Url => ".url()".to_string(),
131 ValidationRule::Required => String::new(), ValidationRule::Custom { name, message } => {
133 if let Some(msg) = message {
134 format!(".refine(validate{name}, {{ message: \"{msg}\" }})")
135 } else {
136 format!(".refine(validate{name})")
137 }
138 }
139 }
140 }
141}
142
143#[cfg(test)]
144mod tests {
145 use super::*;
146
147 #[test]
148 fn test_range_validation() {
149 let rule = ValidationRule::Range {
150 min: Some(1.0),
151 max: Some(100.0),
152 };
153 assert_eq!(
154 rule.to_typescript_check("age"),
155 "obj.age >= 1 && obj.age <= 100"
156 );
157 }
158
159 #[test]
160 fn test_email_validation() {
161 let rule = ValidationRule::Email;
162 assert!(rule.to_typescript_check("email").contains("@"));
163 }
164}