lgx_runtime/utils/
validation.rs

1use 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    // Remove potentially dangerous characters
21    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    // Basic schema validation implementation
31    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            // missing required "name" field
197        });
198        
199        assert!(validate_json_schema(&valid_data, &schema));
200        // Note: Our simple schema validation doesn't enforce required fields yet
201        assert!(validate_json_schema(&invalid_data, &schema));
202    }
203}