1use crate::error::BatchResult;
26use crate::tin::{
27 validate_atin_digits, validate_ein_digits, validate_itin_digits, validate_ssn_digits,
28};
29
30#[derive(Debug, Clone)]
32pub struct ValidationConfig {
33 pub allow_formatted_tins: bool,
35 pub require_both_tins: bool,
37 pub validate_amounts: bool,
39 pub max_batch_size: usize,
41}
42
43impl Default for ValidationConfig {
44 fn default() -> Self {
45 Self {
46 allow_formatted_tins: true,
47 require_both_tins: true,
48 validate_amounts: true,
49 max_batch_size: 10_000,
50 }
51 }
52}
53
54#[derive(Clone)]
57pub struct TinBuffer {
58 data: Vec<u8>,
60 valid: Vec<u64>,
62 count: usize,
64}
65
66impl TinBuffer {
67 pub fn with_capacity(capacity: usize) -> Self {
69 Self {
70 data: Vec::with_capacity(capacity * 9),
71 valid: Vec::with_capacity(capacity.div_ceil(64)),
72 count: 0,
73 }
74 }
75
76 pub fn clear(&mut self) {
78 self.data.clear();
79 self.valid.clear();
80 self.count = 0;
81 }
82
83 pub fn push(&mut self, tin: &str) -> bool {
85 let mut digits = [0u8; 9];
86 let mut digit_count = 0;
87
88 for b in tin.bytes() {
89 let d = b.wrapping_sub(b'0');
90 if d <= 9 {
91 if digit_count >= 9 {
92 self.data.extend_from_slice(&[0; 9]);
94 self.set_valid(self.count, false);
95 self.count += 1;
96 return false;
97 }
98 digits[digit_count] = d;
99 digit_count += 1;
100 } else if b != b'-' && b != b' ' {
101 self.data.extend_from_slice(&[0; 9]);
103 self.set_valid(self.count, false);
104 self.count += 1;
105 return false;
106 }
107 }
108
109 if digit_count == 9 {
110 self.data.extend_from_slice(&digits);
111 self.set_valid(self.count, true);
112 self.count += 1;
113 true
114 } else {
115 self.data.extend_from_slice(&[0; 9]);
116 self.set_valid(self.count, false);
117 self.count += 1;
118 false
119 }
120 }
121
122 fn set_valid(&mut self, index: usize, valid: bool) {
124 let word = index / 64;
125 let bit = index % 64;
126
127 while self.valid.len() <= word {
128 self.valid.push(0);
129 }
130
131 if valid {
132 self.valid[word] |= 1u64 << bit;
133 } else {
134 self.valid[word] &= !(1u64 << bit);
135 }
136 }
137
138 #[inline]
140 pub fn is_format_valid(&self, index: usize) -> bool {
141 let word = index / 64;
142 let bit = index % 64;
143 word < self.valid.len() && (self.valid[word] >> bit) & 1 == 1
144 }
145
146 #[inline]
148 pub fn get_digits(&self, index: usize) -> Option<&[u8; 9]> {
149 if index < self.count {
150 let start = index * 9;
151 Some(self.data[start..start + 9].try_into().unwrap())
152 } else {
153 None
154 }
155 }
156
157 #[inline]
159 pub fn len(&self) -> usize {
160 self.count
161 }
162
163 #[inline]
165 pub fn is_empty(&self) -> bool {
166 self.count == 0
167 }
168}
169
170#[derive(Clone)]
173pub struct AmountBuffer {
174 data: Vec<i64>,
176}
177
178impl AmountBuffer {
179 pub fn with_capacity(capacity: usize) -> Self {
181 Self {
182 data: Vec::with_capacity(capacity),
183 }
184 }
185
186 pub fn clear(&mut self) {
188 self.data.clear();
189 }
190
191 #[inline]
193 pub fn push(&mut self, cents: i64) {
194 self.data.push(cents);
195 }
196
197 #[inline]
199 pub fn push_dollars(&mut self, dollars: f64) {
200 self.data.push((dollars * 100.0).round() as i64);
201 }
202
203 #[inline]
205 pub fn get(&self, index: usize) -> Option<i64> {
206 self.data.get(index).copied()
207 }
208
209 #[inline]
211 pub fn len(&self) -> usize {
212 self.data.len()
213 }
214
215 #[inline]
217 pub fn is_empty(&self) -> bool {
218 self.data.is_empty()
219 }
220
221 pub fn validate_non_negative(&self) -> BatchResult {
223 let mut result = BatchResult::with_capacity(self.data.len());
224
225 for (i, &amount) in self.data.iter().enumerate() {
227 result.set(i, amount >= 0);
228 }
229
230 result
231 }
232}
233
234pub struct FormBatch {
238 pub payer_tins: TinBuffer,
240 pub recipient_tins: TinBuffer,
242 pub interest_income: AmountBuffer,
244 pub tax_years: Vec<u16>,
246 config: ValidationConfig,
248}
249
250impl FormBatch {
251 pub fn with_capacity(capacity: usize) -> Self {
253 Self {
254 payer_tins: TinBuffer::with_capacity(capacity),
255 recipient_tins: TinBuffer::with_capacity(capacity),
256 interest_income: AmountBuffer::with_capacity(capacity),
257 tax_years: Vec::with_capacity(capacity),
258 config: ValidationConfig::default(),
259 }
260 }
261
262 pub fn with_config(capacity: usize, config: ValidationConfig) -> Self {
264 Self {
265 payer_tins: TinBuffer::with_capacity(capacity),
266 recipient_tins: TinBuffer::with_capacity(capacity),
267 interest_income: AmountBuffer::with_capacity(capacity),
268 tax_years: Vec::with_capacity(capacity),
269 config,
270 }
271 }
272
273 pub fn clear(&mut self) {
275 self.payer_tins.clear();
276 self.recipient_tins.clear();
277 self.interest_income.clear();
278 self.tax_years.clear();
279 }
280
281 #[inline]
283 pub fn len(&self) -> usize {
284 self.payer_tins.len()
285 }
286
287 #[inline]
289 pub fn is_empty(&self) -> bool {
290 self.payer_tins.is_empty()
291 }
292
293 pub fn push_tins(&mut self, payer_tin: &str, recipient_tin: &str) {
295 self.payer_tins.push(payer_tin);
296 self.recipient_tins.push(recipient_tin);
297 }
298
299 pub fn push_income(&mut self, cents: i64) {
301 self.interest_income.push(cents);
302 }
303
304 pub fn push_tax_year(&mut self, year: u16) {
306 self.tax_years.push(year);
307 }
308
309 pub fn validate(&self) -> BatchResult {
313 let count = self.len();
314 let mut result = BatchResult::with_capacity(count);
315
316 for i in 0..count {
317 let mut valid = true;
318
319 if self.payer_tins.is_format_valid(i) {
321 if let Some(digits) = self.payer_tins.get_digits(i) {
322 valid &= is_valid_tin_digits(digits);
323 }
324 } else {
325 valid = false;
326 }
327
328 if self.config.require_both_tins {
330 if self.recipient_tins.is_format_valid(i) {
331 if let Some(digits) = self.recipient_tins.get_digits(i) {
332 valid &= is_valid_tin_digits(digits);
333 }
334 } else {
335 valid = false;
336 }
337 }
338
339 if self.config.validate_amounts {
341 if let Some(amount) = self.interest_income.get(i) {
342 valid &= amount >= 0;
343 }
344 }
345
346 if let Some(&year) = self.tax_years.get(i) {
348 valid &= (2020..=2100).contains(&year);
349 }
350
351 result.set(i, valid);
352 }
353
354 result
355 }
356
357 pub fn validate_tins_only(&self) -> BatchResult {
359 let count = self.len();
360 let mut result = BatchResult::with_capacity(count);
361
362 for i in 0..count {
363 let payer_valid = self.payer_tins.is_format_valid(i)
364 && self
365 .payer_tins
366 .get_digits(i)
367 .map(is_valid_tin_digits)
368 .unwrap_or(false);
369
370 let recipient_valid = !self.config.require_both_tins
371 || (self.recipient_tins.is_format_valid(i)
372 && self
373 .recipient_tins
374 .get_digits(i)
375 .map(is_valid_tin_digits)
376 .unwrap_or(false));
377
378 result.set(i, payer_valid && recipient_valid);
379 }
380
381 result
382 }
383}
384
385#[inline]
387fn is_valid_tin_digits(digits: &[u8; 9]) -> bool {
388 if digits.iter().all(|&d| d == 0) {
390 return false;
391 }
392
393 validate_ssn_digits(digits).is_ok()
395 || validate_ein_digits(digits).is_ok()
396 || validate_itin_digits(digits)
397 || validate_atin_digits(digits)
398}
399
400#[cfg(feature = "rayon")]
406pub fn validate_parallel(batches: &[FormBatch]) -> Vec<BatchResult> {
407 use rayon::prelude::*;
408 batches.par_iter().map(|b| b.validate()).collect()
409}
410
411#[cfg(test)]
416mod tests {
417 use super::*;
418
419 #[test]
420 fn test_tin_buffer() {
421 let mut buf = TinBuffer::with_capacity(10);
422
423 assert!(buf.push("123456789"));
424 assert!(buf.push("123-45-6789"));
425 assert!(!buf.push("12345678")); assert!(!buf.push("1234567890")); assert!(!buf.push("12345678a")); assert_eq!(buf.len(), 5);
430 assert!(buf.is_format_valid(0));
431 assert!(buf.is_format_valid(1));
432 assert!(!buf.is_format_valid(2));
433 assert!(!buf.is_format_valid(3));
434 assert!(!buf.is_format_valid(4));
435
436 assert_eq!(buf.get_digits(0), Some(&[1, 2, 3, 4, 5, 6, 7, 8, 9]));
437 assert_eq!(buf.get_digits(1), Some(&[1, 2, 3, 4, 5, 6, 7, 8, 9]));
438 }
439
440 #[test]
441 fn test_amount_buffer() {
442 let mut buf = AmountBuffer::with_capacity(10);
443
444 buf.push(10000); buf.push(-5000); buf.push(0);
447 buf.push_dollars(123.45);
448
449 assert_eq!(buf.get(0), Some(10000));
450 assert_eq!(buf.get(1), Some(-5000));
451 assert_eq!(buf.get(2), Some(0));
452 assert_eq!(buf.get(3), Some(12345));
453
454 let result = buf.validate_non_negative();
455 assert!(result.is_valid(0));
456 assert!(!result.is_valid(1));
457 assert!(result.is_valid(2));
458 assert!(result.is_valid(3));
459 }
460
461 #[test]
462 fn test_form_batch() {
463 let mut batch = FormBatch::with_capacity(10);
464
465 batch.push_tins("123-45-6789", "987-65-4321");
467 batch.push_income(10000);
468 batch.push_tax_year(2024);
469
470 batch.push_tins("000-00-0000", "123-45-6789");
472 batch.push_income(5000);
473 batch.push_tax_year(2024);
474
475 batch.push_tins("123-45-6789", "987-65-4321");
477 batch.push_income(-1000);
478 batch.push_tax_year(2024);
479
480 let result = batch.validate();
481
482 assert!(result.is_valid(0));
483 assert!(!result.is_valid(1)); assert!(!result.is_valid(2)); assert_eq!(result.valid_count(), 1);
487 assert_eq!(result.invalid_count(), 2);
488 }
489
490 #[test]
491 fn test_batch_reuse() {
492 let mut batch = FormBatch::with_capacity(10);
493
494 batch.push_tins("123-45-6789", "987-65-4321");
495 batch.push_income(10000);
496 batch.push_tax_year(2024);
497
498 assert_eq!(batch.len(), 1);
499
500 batch.clear();
501
502 assert_eq!(batch.len(), 0);
503 assert!(batch.is_empty());
504
505 batch.push_tins("111-22-3333", "444-55-6666");
507 assert_eq!(batch.len(), 1);
508 }
509}