File size: 4,821 Bytes
7856e60
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
use once_cell::sync::Lazy;
use regex::Regex;

static PHONE_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"^[6-9]\d{9}$").unwrap());

static EMAIL_REGEX: Lazy<Regex> =
    Lazy::new(|| Regex::new(r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$").unwrap());

static PINCODE_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"^[1-9]\d{5}$").unwrap());

pub fn validate_email(email: &str) -> Result<(), ValidationError> {
    if email.is_empty() {
        return Err(ValidationError::new("Email cannot be empty"));
    }
    if email.len() > 255 {
        return Err(ValidationError::new("Email too long"));
    }
    if !EMAIL_REGEX.is_match(email) {
        return Err(ValidationError::new("Invalid email format"));
    }
    Ok(())
}

pub fn validate_pincode(pincode: &str) -> Result<(), ValidationError> {
    if pincode.is_empty() {
        return Err(ValidationError::new("Pincode cannot be empty"));
    }
    if !PINCODE_REGEX.is_match(pincode) {
        return Err(ValidationError::new(
            "Invalid Indian pincode format (6 digits starting with 1-9)",
        ));
    }
    Ok(())
}

pub fn validate_price(price: f64) -> Result<(), ValidationError> {
    if price <= 0.0 {
        return Err(ValidationError::new("Price must be greater than 0"));
    }
    if price > 1_000_000.0 {
        return Err(ValidationError::new("Price exceeds maximum allowed value"));
    }
    if price.is_nan() || price.is_infinite() {
        return Err(ValidationError::new("Invalid price value"));
    }
    Ok(())
}

pub fn validate_product_name(name: &str) -> Result<(), ValidationError> {
    if name.trim().is_empty() {
        return Err(ValidationError::new("Product name cannot be empty"));
    }
    if name.len() > 200 {
        return Err(ValidationError::new(
            "Product name too long (max 200 characters)",
        ));
    }
    Ok(())
}

pub fn validate_weight(weight: f64) -> Result<(), ValidationError> {
    if weight < 0.0 {
        return Err(ValidationError::new("Weight cannot be negative"));
    }
    if weight > 100_000.0 {
        // 100kg limit for social checkout
        return Err(ValidationError::new(
            "Weight exceeds maximum allowed value (100kg)",
        ));
    }
    if weight.is_nan() || weight.is_infinite() {
        return Err(ValidationError::new("Invalid weight value"));
    }
    Ok(())
}

pub fn sanitize_string(input: &str) -> String {
    input
        .trim()
        .chars()
        .filter(|c| !c.is_control())
        .take(1000)
        .collect()
}

pub fn normalize_phone(phone: &str) -> Result<String, ValidationError> {
    if phone.is_empty() {
        return Err(ValidationError::new("Phone cannot be empty"));
    }

    let digits: String = phone.chars().filter(|c| c.is_ascii_digit()).collect();
    let normalized = match digits.len() {
        10 => digits,
        11 if digits.starts_with('0') => digits[1..].to_string(),
        12 if digits.starts_with("91") => digits[2..].to_string(),
        _ => return Err(ValidationError::new("Invalid Indian phone number format")),
    };

    if !PHONE_REGEX.is_match(&normalized) {
        return Err(ValidationError::new("Invalid Indian phone number format"));
    }

    Ok(normalized)
}

pub fn validate_base64_payload(data: &str, max_bytes: usize) -> Result<Vec<u8>, ValidationError> {
    if data.is_empty() {
        return Err(ValidationError::new("Base64 payload cannot be empty"));
    }

    let raw_data = if let Some(pos) = data.find(',') {
        &data[pos + 1..]
    } else {
        data
    };

    use base64::{engine::general_purpose, Engine as _};
    match general_purpose::STANDARD.decode(raw_data) {
        Ok(bytes) => {
            if bytes.len() > max_bytes {
                return Err(ValidationError::new(&format!(
                    "Payload exceeds maximum size of {} bytes",
                    max_bytes
                )));
            }
            Ok(bytes)
        }
        Err(_) => Err(ValidationError::new("Invalid Base64 encoding")),
    }
}

pub fn sanitize_filename(filename: &str) -> String {
    let sanitized: String = filename
        .replace("..", "")
        .chars()
        .filter(|c| c.is_alphanumeric() || *c == '.' || *c == '-' || *c == '_')
        .collect();

    if sanitized.starts_with('.') || sanitized.is_empty() {
        return "unnamed".to_string();
    }

    sanitized.chars().take(255).collect()
}

#[derive(Debug, Clone)]
pub struct ValidationError {
    pub message: String,
}

impl ValidationError {
    pub fn new(msg: &str) -> Self {
        Self {
            message: msg.to_string(),
        }
    }
}

impl std::fmt::Display for ValidationError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.message)
    }
}

impl std::error::Error for ValidationError {}