Skip to main content

wellformed_validate/
error.rs

1//! Error types for validation.
2
3use thiserror::Error;
4
5/// Validation error types.
6#[derive(Debug, Error, Clone, PartialEq, Eq)]
7pub enum ValidationError {
8    #[error("invalid TIN format: expected 9 digits")]
9    InvalidTinFormat,
10
11    #[error("invalid SSN: {0}")]
12    InvalidSsn(SsnError),
13
14    #[error("invalid EIN: {0}")]
15    InvalidEin(EinError),
16
17    #[error("invalid ITIN: must start with 9 and have 7 or 8 in position 4")]
18    InvalidItin,
19
20    #[error("pattern match failed: {0}")]
21    PatternFailed(String),
22
23    #[error("batch validation failed: {failed} of {total} items invalid")]
24    BatchFailed { failed: usize, total: usize },
25}
26
27/// SSN-specific validation errors.
28#[derive(Debug, Clone, Copy, PartialEq, Eq)]
29pub enum SsnError {
30    /// Area number is 000
31    AreaZero,
32    /// Area number is 666
33    Area666,
34    /// Area number is 900-999 (reserved for ITINs)
35    AreaReserved,
36    /// Group number is 00
37    GroupZero,
38    /// Serial number is 0000
39    SerialZero,
40    /// All digits are the same
41    AllSameDigits,
42}
43
44impl std::fmt::Display for SsnError {
45    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
46        match self {
47            SsnError::AreaZero => write!(f, "area number cannot be 000"),
48            SsnError::Area666 => write!(f, "area number cannot be 666"),
49            SsnError::AreaReserved => write!(f, "area number 900-999 reserved for ITIN"),
50            SsnError::GroupZero => write!(f, "group number cannot be 00"),
51            SsnError::SerialZero => write!(f, "serial number cannot be 0000"),
52            SsnError::AllSameDigits => write!(f, "all digits cannot be the same"),
53        }
54    }
55}
56
57/// EIN-specific validation errors.
58#[derive(Debug, Clone, Copy, PartialEq, Eq)]
59pub enum EinError {
60    /// Invalid campus/prefix code
61    InvalidCampus,
62    /// All zeros
63    AllZeros,
64}
65
66impl std::fmt::Display for EinError {
67    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
68        match self {
69            EinError::InvalidCampus => write!(f, "invalid IRS campus code"),
70            EinError::AllZeros => write!(f, "EIN cannot be all zeros"),
71        }
72    }
73}
74
75/// Result type for validation operations.
76pub type ValidationResult<T> = Result<T, ValidationError>;
77
78/// Batch validation result with bit-packed valid flags.
79#[derive(Debug, Clone)]
80pub struct BatchResult {
81    /// Bit vector of valid flags (1 = valid, 0 = invalid)
82    valid_bits: Vec<u64>,
83    /// Number of items validated
84    count: usize,
85    /// Number of valid items
86    valid_count: usize,
87}
88
89impl BatchResult {
90    /// Create a new batch result with given capacity.
91    pub fn with_capacity(count: usize) -> Self {
92        let num_words = count.div_ceil(64);
93        Self {
94            valid_bits: vec![0; num_words],
95            count,
96            valid_count: 0,
97        }
98    }
99
100    /// Set the validation result for an index.
101    #[inline]
102    pub fn set(&mut self, index: usize, valid: bool) {
103        let word = index / 64;
104        let bit = index % 64;
105        if valid {
106            self.valid_bits[word] |= 1u64 << bit;
107            self.valid_count += 1;
108        }
109    }
110
111    /// Check if an index is valid.
112    #[inline]
113    pub fn is_valid(&self, index: usize) -> bool {
114        let word = index / 64;
115        let bit = index % 64;
116        (self.valid_bits[word] >> bit) & 1 == 1
117    }
118
119    /// Get the number of valid items.
120    #[inline]
121    pub fn valid_count(&self) -> usize {
122        self.valid_count
123    }
124
125    /// Get the number of invalid items.
126    #[inline]
127    pub fn invalid_count(&self) -> usize {
128        self.count - self.valid_count
129    }
130
131    /// Check if all items are valid.
132    #[inline]
133    pub fn all_valid(&self) -> bool {
134        self.valid_count == self.count
135    }
136
137    /// Iterate over indices of invalid items.
138    pub fn invalid_indices(&self) -> impl Iterator<Item = usize> + '_ {
139        (0..self.count).filter(|&i| !self.is_valid(i))
140    }
141
142    /// Iterate over indices of valid items.
143    pub fn valid_indices(&self) -> impl Iterator<Item = usize> + '_ {
144        (0..self.count).filter(|&i| self.is_valid(i))
145    }
146}
147
148#[cfg(test)]
149mod tests {
150    use super::*;
151
152    #[test]
153    fn test_batch_result() {
154        let mut result = BatchResult::with_capacity(100);
155
156        result.set(0, true);
157        result.set(1, false);
158        result.set(63, true);
159        result.set(64, true);
160        result.set(99, false);
161
162        assert!(result.is_valid(0));
163        assert!(!result.is_valid(1));
164        assert!(result.is_valid(63));
165        assert!(result.is_valid(64));
166        assert!(!result.is_valid(99));
167
168        assert_eq!(result.valid_count(), 3);
169        assert_eq!(result.invalid_count(), 97);
170    }
171}