wellformed_validate/
error.rs1use thiserror::Error;
4
5#[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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
29pub enum SsnError {
30 AreaZero,
32 Area666,
34 AreaReserved,
36 GroupZero,
38 SerialZero,
40 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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
59pub enum EinError {
60 InvalidCampus,
62 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
75pub type ValidationResult<T> = Result<T, ValidationError>;
77
78#[derive(Debug, Clone)]
80pub struct BatchResult {
81 valid_bits: Vec<u64>,
83 count: usize,
85 valid_count: usize,
87}
88
89impl BatchResult {
90 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 #[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 #[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 #[inline]
121 pub fn valid_count(&self) -> usize {
122 self.valid_count
123 }
124
125 #[inline]
127 pub fn invalid_count(&self) -> usize {
128 self.count - self.valid_count
129 }
130
131 #[inline]
133 pub fn all_valid(&self) -> bool {
134 self.valid_count == self.count
135 }
136
137 pub fn invalid_indices(&self) -> impl Iterator<Item = usize> + '_ {
139 (0..self.count).filter(|&i| !self.is_valid(i))
140 }
141
142 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}