lgx_runtime/utils/
validation.rs1use regex::Regex;
2use tracing::debug;
3
4pub fn validate_email(email: &str) -> bool {
5 debug!("Validating email: {}", email);
6
7 let email_regex = Regex::new(r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$").unwrap();
8 email_regex.is_match(email)
9}
10
11pub fn validate_uuid(uuid_str: &str) -> bool {
12 debug!("Validating UUID: {}", uuid_str);
13
14 uuid::Uuid::parse_str(uuid_str).is_ok()
15}
16
17pub fn sanitize_input(input: &str) -> String {
18 debug!("Sanitizing input of length: {}", input.len());
19
20 let dangerous_chars = Regex::new(r#"[<>"'&]"#).unwrap();
22 let sanitized = dangerous_chars.replace_all(input, "");
23
24 sanitized.trim().to_string()
25}
26
27pub fn validate_json_schema(json: &serde_json::Value, schema: &serde_json::Value) -> bool {
28 debug!("Validating JSON against schema");
29
30 match (json, schema) {
32 (serde_json::Value::Object(json_obj), serde_json::Value::Object(schema_obj)) => {
33 for (key, schema_value) in schema_obj {
34 if let Some(json_value) = json_obj.get(key) {
35 if !validate_json_value(json_value, schema_value) {
36 return false;
37 }
38 } else if schema_obj.get("required").map_or(false, |r| r.as_bool().unwrap_or(false)) {
39 return false;
40 }
41 }
42 true
43 }
44 (serde_json::Value::Array(json_arr), serde_json::Value::Object(schema_obj)) => {
45 if let Some(serde_json::Value::String(item_type)) = schema_obj.get("type") {
46 match item_type.as_str() {
47 "array" => {
48 for item in json_arr {
49 if !validate_json_value(item, schema) {
50 return false;
51 }
52 }
53 true
54 }
55 _ => true
56 }
57 } else {
58 true
59 }
60 }
61 _ => true
62 }
63}
64
65fn validate_json_value(value: &serde_json::Value, schema: &serde_json::Value) -> bool {
66 match (value, schema) {
67 (serde_json::Value::String(s), serde_json::Value::Object(schema_obj)) => {
68 if let Some(serde_json::Value::String(expected_type)) = schema_obj.get("type") {
69 match expected_type.as_str() {
70 "string" => {
71 if let Some(serde_json::Value::Number(min_len)) = schema_obj.get("minLength") {
72 if s.len() < min_len.as_u64().unwrap_or(0) as usize {
73 return false;
74 }
75 }
76 if let Some(serde_json::Value::Number(max_len)) = schema_obj.get("maxLength") {
77 if s.len() > max_len.as_u64().unwrap_or(0) as usize {
78 return false;
79 }
80 }
81 true
82 }
83 "email" => validate_email(s),
84 "uuid" => validate_uuid(s),
85 _ => true
86 }
87 } else {
88 true
89 }
90 }
91 (serde_json::Value::Number(n), serde_json::Value::Object(schema_obj)) => {
92 if let Some(serde_json::Value::String(expected_type)) = schema_obj.get("type") {
93 match expected_type.as_str() {
94 "number" | "integer" => {
95 if let Some(serde_json::Value::Number(min_val)) = schema_obj.get("minimum") {
96 if n.as_f64().unwrap_or(0.0) < min_val.as_f64().unwrap_or(0.0) {
97 return false;
98 }
99 }
100 if let Some(serde_json::Value::Number(max_val)) = schema_obj.get("maximum") {
101 if n.as_f64().unwrap_or(0.0) > max_val.as_f64().unwrap_or(0.0) {
102 return false;
103 }
104 }
105 true
106 }
107 _ => true
108 }
109 } else {
110 true
111 }
112 }
113 (serde_json::Value::Bool(_), serde_json::Value::Object(schema_obj)) => {
114 if let Some(serde_json::Value::String(expected_type)) = schema_obj.get("type") {
115 expected_type == "boolean"
116 } else {
117 true
118 }
119 }
120 _ => true
121 }
122}
123
124pub fn validate_required_fields(data: &serde_json::Value, required_fields: &[String]) -> bool {
125 debug!("Validating required fields: {:?}", required_fields);
126
127 if let serde_json::Value::Object(obj) = data {
128 for field in required_fields {
129 if !obj.contains_key(field) {
130 return false;
131 }
132 }
133 true
134 } else {
135 false
136 }
137}
138
139#[cfg(test)]
140mod tests {
141 use super::*;
142
143 #[test]
144 fn test_validate_email() {
145 assert!(validate_email("test@example.com"));
146 assert!(validate_email("user.name+tag@domain.co.uk"));
147 assert!(!validate_email("invalid-email"));
148 assert!(!validate_email("test@"));
149 assert!(!validate_email("@domain.com"));
150 }
151
152 #[test]
153 fn test_validate_uuid() {
154 assert!(validate_uuid("123e4567-e89b-12d3-a456-426614174000"));
155 assert!(!validate_uuid("invalid-uuid"));
156 assert!(!validate_uuid("123e4567-e89b-12d3-a456"));
157 }
158
159 #[test]
160 fn test_sanitize_input() {
161 assert_eq!(sanitize_input("Hello <script>alert('xss')</script>"), "Hello scriptalert(xss)/script");
162 assert_eq!(sanitize_input("Normal text"), "Normal text");
163 assert_eq!(sanitize_input(" whitespace "), "whitespace");
164 }
165
166 #[test]
167 fn test_validate_required_fields() {
168 let data = serde_json::json!({
169 "name": "John",
170 "email": "john@example.com",
171 "age": 30
172 });
173
174 assert!(validate_required_fields(&data, &["name".to_string(), "email".to_string()]));
175 assert!(!validate_required_fields(&data, &["name".to_string(), "missing".to_string()]));
176 assert!(!validate_required_fields(&serde_json::Value::String("not an object".to_string()), &["name".to_string()]));
177 }
178
179 #[test]
180 fn test_validate_json_schema() {
181 let schema = serde_json::json!({
182 "type": "object",
183 "properties": {
184 "name": { "type": "string" },
185 "age": { "type": "number" }
186 }
187 });
188
189 let valid_data = serde_json::json!({
190 "name": "John",
191 "age": 30
192 });
193
194 let invalid_data = serde_json::json!({
195 "age": 30
196 });
198
199 assert!(validate_json_schema(&valid_data, &schema));
200 assert!(validate_json_schema(&invalid_data, &schema));
202 }
203}