document_validator 0.2.0

Document validation library
Documentation
use std::sync::Arc;

use super::Document;
use crate::prelude::*;

const CNPJ_LENGTH: usize = 14;

#[derive(PartialEq, Eq, Hash)]
pub struct Cnpj(Arc<str>);

impl AsRef<str> for Cnpj {
    fn as_ref(&self) -> &str {
        self.0.as_ref()
    }
}

impl Cnpj {
    fn generate_digit(digits: &[u8]) -> u8 {
        let c = digits.len();
        let result = digits
            .iter()
            .enumerate()
            .map(|(index, digit)| *digit as usize * ((c - 1 - index) % 8 + 2))
            .sum::<usize>();

        let result = 11 - (result % 11);

        if result < 10 {
            result as u8
        } else {
            0
        }
    }
}

impl Document for Cnpj {
    fn new(document: impl Into<Arc<str>>) -> Result<Self> {
        let document: Arc<str> = document
            .into()
            .chars()
            .filter(char::is_ascii_digit)
            .collect::<String>()
            .into();

        if Self::validate(Arc::clone(&document)) {
            Ok(Self(document))
        } else {
            Err(Error::ParseError(document))
        }
    }

    fn validate(document: impl Into<Arc<str>>) -> bool {
        let document: Arc<str> = document.into();

        let digits = document
            .bytes()
            .filter(u8::is_ascii_digit)
            .map(|x| x - b'0')
            .collect::<Vec<_>>();

        if digits.len() != CNPJ_LENGTH {
            return false;
        }

        let first_digit = Self::generate_digit(&digits[..CNPJ_LENGTH - 2]);

        if first_digit != digits[CNPJ_LENGTH - 2] {
            return false;
        }

        let second_digit = Self::generate_digit(&digits[..CNPJ_LENGTH - 1]);

        second_digit == digits[CNPJ_LENGTH - 1]
    }

    fn document_name(&self) -> &'static str {
        "cnpj"
    }
}