From 5a8fab20db0020cf13de281861a8ea5e100c5a2a Mon Sep 17 00:00:00 2001 From: Breno Viana Date: Sat, 16 Mar 2024 15:06:03 -0300 Subject: [PATCH] =?UTF-8?q?Adiciona=20documenta=C3=A7=C3=A3o=20(#52)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * descreve funções no README.md * atualiza README.md * adiciona documentação geral da lib * adiciona documentação de CPF e corrige função is_masked * adiciona documentação de CNPJ e corrige função is_masked * adiciona documentação de CNH e corrige função is_masked * adiciona documentação dos módulos docs e common * pequeno ajuste na documentação --- README.md | 91 ++++++++++++++++++++---- brado/src/cnh.rs | 158 ++++++++++++++++++++++++++++++++++++------ brado/src/cnpj.rs | 164 +++++++++++++++++++++++++++++++++++++------- brado/src/common.rs | 78 ++++++++++++++++++--- brado/src/cpf.rs | 158 ++++++++++++++++++++++++++++++++++++------ brado/src/docs.rs | 95 +++++++++++++++++++++++-- brado/src/lib.rs | 24 +++++-- brado/tests/cnpj.rs | 20 +++++- brado/tests/cpf.rs | 18 +++++ 9 files changed, 701 insertions(+), 105 deletions(-) diff --git a/README.md b/README.md index 7480b4d..7d25867 100644 --- a/README.md +++ b/README.md @@ -10,30 +10,32 @@ latest release -Brado (BRAzilian DOcs validator) é um pacote Rust para validação de documentos brasileiros. -Este projeto é inspirado no [validate-docbr](https://github.com/alvarofpp/validate-docbr). +Brado é um pacote Rust para validação de documentos brasileiros. Este projeto é inspirado na biblioteca Python [validate-docbr](https://github.com/alvarofpp/validate-docbr). -Para adicionar o pacote ao projeto: +Brado fornece funções para identificação, validação e geração de documentos brasileiros. O nome desta biblioteca (Brado) é um acronimo de BRAzilian DOcs validator (validador de DOcumentos BRAsileiros). -```bash -cargo add brado -``` +> :warning: A documentação desta biblioteca pode ser acessada [aqui](https://docs.rs/brado/). -A documentação pode ser acessada [aqui](https://docs.rs/brado/) (ainda em desenvolvimento). +## Guia Rápido -## Testes - -Para rodar os testes, basta executar o comando a seguir: +Para adicionar o pacote ao projeto, basta rodar o seguinte comando: +```bash +cargo add brado ``` -make test + +Ou adicionar a linha a seguir no arquivo `Cargo.toml`: + +```toml +brado = "0.3.5" ``` + ## Documentos -- [x] CPF: Cadastro de Pessoas Físicas; +- [x] CPF: Cadastro de Pessoa Física; - [x] CNH: Carteira Nacional de Habilitação; - [x] CNPJ: Cadastro Nacional da Pessoa Jurídica; - [ ] CNS: Cartão Nacional de Saúde; @@ -49,30 +51,80 @@ Todos os documentos possuem as mesmas funções e funcionam da mesma forma. ### validate +Valida o documento passado como parâmetro (`&str`). Retorna um valor booleano (`bool`), `true` caso o documento seja válido, ou `false` caso contrário. + ```rust use brado::cpf; +cpf::validate("63929247011"); // true cpf::validate("639.292.470-11"); // true -cpf::validate("639.292.470-10"); // false -cpf::validate("63929247011"); // true cpf::validate("63929247010"); // false +cpf::validate("639.292.470-10"); // false ``` ### mask +Mascara o documento passado como parâmetro (`&str`). Retorna a string (`String`) correspondente ao documento mascarado. A string passada não deve possuir símbolos. + ```rust use brado::cpf; cpf::mask("63929247011"); // "639.292.470-11" + +cpf::mask("639.292.470-11"); // panic! +``` + +### is_bare + +Verifica se o documento passado como parâmetro (`&str`) não possui símbolos. Retorna um valor booleano (`bool`), `true` caso o documento não possua símbolos, ou `false` caso contrário. + +```rust +use brado::cpf; + +cpf::is_bare("63929247011"); // true +cpf::is_bare("63929247010"); // true + +cpf::is_bare("639.292.470-11"); // false +cpf::is_bare("639.29247011"); // false +cpf::is_bare("639292470110"); // false ``` +> OBS: se for utilizada a função `cpf::is_bare` para verificar se um CNPJ não possui símbolos, o resultado será `false`! Isso acontece pois esta função considera que a string é um CPF, ou seja, possui 11 dígitos. + +### is_masked + +Verifica se o documento passado como parâmetro (`&str`) está mascarado de acordo com o documento correspondente. Retorna um valor booleano (`bool`), `true` caso o documento esteja mascarado, ou `false` caso contrário. + +```rust +use brado::cpf; + +cpf::is_masked("639.292.470-10"); // true + +cpf::is_masked("63929247011"); // false +cpf::is_masked("6392.92.470-11"); // false +cpf::is_masked("639.292.470-110"); // false +``` + +> OBS: `cpf::is_masked` verifica se a string passada está mascarada como um CPF. `cnpj::is_masked` verifica se a string passada está mascarada como um CNPJ. + ### generate +Gera um novo documento sem símbolos (`String`). + +```rust +use brado::cpf; + +cpf::generate(); // "639.292.470-11" +``` + +### generate_masked + +Gera um novo documento mascarado (`String`). + ```rust use brado::cpf; -cpf::generate(); // "63929247011" cpf::generate_masked(); // "639.292.470-11" ``` @@ -87,3 +139,12 @@ nix flake clone github:your-github-user/brado --dest ./brado \ && cd brado 1>/dev/null 2>/dev/null \ && direnv allow ``` + + +## Testes + +Para rodar os testes, basta executar o comando a seguir: + +```bash +make test +``` diff --git a/brado/src/cnh.rs b/brado/src/cnh.rs index 6bfc1ea..e96ed13 100644 --- a/brado/src/cnh.rs +++ b/brado/src/cnh.rs @@ -1,15 +1,44 @@ +//! Utilitários para validação de CNH. + use crate::common::{ get_digits, get_symbols, is_repeated, random_digit_vector, }; -pub fn validate(cnh: &str) -> bool { - let size: usize = cnh.chars().count(); - - if size != 11 && !is_masked(cnh) { +/// Realiza validação de CNH, máscarado ou não. +/// Retorna `true` se o argumento `doc` for uma CNH válido, +/// caso contrário, retorna `false`. +/// +/// ## Exemplos +/// +/// CNHs válidos: +/// ``` +/// use brado::cnh; +/// +/// let result = cnh::validate("84718735264"); // true +/// assert!(result); +/// +/// let result = cnh::validate("847 187 352 64"); // true +/// assert!(result); +/// ``` +/// +/// CNHs inválidos: +/// ``` +/// use brado::cnh; +/// +/// let result = cnh::validate("84718735265"); // false +/// assert!(!result); +/// +/// let result = cnh::validate("847 187 352 65"); // false +/// assert!(!result); +/// ``` +pub fn validate(doc: &str) -> bool { + let size: usize = doc.chars().count(); + + if size != 11 && !is_masked(doc) { return false; } - let digits: Vec = get_digits(cnh); + let digits: Vec = get_digits(doc); if digits.len() != 11 { return false; @@ -24,9 +53,9 @@ pub fn validate(cnh: &str) -> bool { (d10, d11) == (digits[9], digits[10]) } -fn generate_digits(cnh_slice: &[u16]) -> (u16, u16) { - let (d10, dsc): (u16, u16) = generate_first_digit(cnh_slice); - let d11: u16 = generate_second_digit(cnh_slice, dsc); +fn generate_digits(doc_slice: &[u16]) -> (u16, u16) { + let (d10, dsc): (u16, u16) = generate_first_digit(doc_slice); + let d11: u16 = generate_second_digit(doc_slice, dsc); (d10, d11) } @@ -50,13 +79,13 @@ fn generate_first_digit(cnh: &[u16]) -> (u16, u16) { } fn generate_second_digit( - cnh: &[u16], + doc: &[u16], dsc: u16, ) -> u16 { let mut sum: u16 = 0; for i in 1..=9 { - sum += cnh[i - 1] * (i as u16); + sum += doc[i - 1] * (i as u16); } let mut second: i16 = ((sum % 11) as i16) - (dsc as i16); @@ -70,34 +99,110 @@ fn generate_second_digit( second as u16 } -pub fn is_bare(cnh: &str) -> bool { - cnh.chars().count() == 11 && get_digits(cnh).len() == 11 +/// Verifica se o argumento `doc` pode ser um CNH sem símbolos. +/// Se for, retorna `true`, caso contrário, retorna `false`. +/// +/// ## Exemplos +/// +/// CNHs válidos: +/// ``` +/// use brado::cnh; +/// +/// let result = cnh::is_bare("84718735264"); // true +/// assert!(result); +/// +/// let result = cnh::is_bare("847 187 352 64"); // false +/// assert!(!result); +/// ``` +/// +/// CNHs inválidos: +/// ``` +/// use brado::cnh; +/// +/// let result = cnh::is_bare("84718735265"); // true +/// assert!(result); +/// ``` +pub fn is_bare(doc: &str) -> bool { + doc.chars().count() == 11 && get_digits(doc).len() == 11 } -pub fn is_masked(cnh: &str) -> bool { - let symbols: Vec<(usize, char)> = get_symbols(cnh); - - if symbols.len() != 3 { +/// Verifica se o argumento `doc` pode ser um CNH com símbolos. +/// Se for, retorna `true`, caso contrário, retorna `false`. +/// +/// ## Exemplos +/// +/// CNHs válidos: +/// ``` +/// use brado::cnh; +/// +/// let result = cnh::is_masked("847 187 352 64"); // true +/// assert!(result); +/// +/// let result = cnh::is_masked("84718735264"); // false +/// assert!(!result); +/// ``` +/// +/// CNHs inválidos: +/// ``` +/// use brado::cnh; +/// +/// let result = cnh::is_masked("847 187 352 65"); // true +/// assert!(result); +/// ``` +pub fn is_masked(doc: &str) -> bool { + let symbols: Vec<(usize, char)> = get_symbols(doc); + let digits: Vec = get_digits(doc); + + if symbols.len() != 3 || digits.len() != 11 { return false; } symbols[0] == (3, ' ') && symbols[1] == (7, ' ') && symbols[2] == (11, ' ') } -pub fn mask(cnh: &str) -> String { - if !is_bare(cnh) { +/// Aplica máscara de CNH no argumento `doc` e retorna resultado. +/// O argumento deve ser uma string sem símbolos, caso contrário, +/// deve lançar erro. +/// +/// ## Exemplos +/// +/// Documento de 11 dígitos sem máscara: +/// ``` +/// use brado::cnh; +/// +/// let result = cnh::mask("84718735264"); // "847 187 352 64" +/// assert!(cnh::is_masked(&result)); // true +/// ``` +/// +/// Documento de 11 dígitos com máscara: +/// ```should_panic +/// use brado::cnh; +/// +/// cnh::mask("847 187 352 64"); // panic! +/// ``` +pub fn mask(doc: &str) -> String { + if !is_bare(doc) { panic!("The given string cannot be masked as CNH!") } format!( "{} {} {} {}", - &cnh[0..3], - &cnh[3..6], - &cnh[6..9], - &cnh[9..11], + &doc[0..3], + &doc[3..6], + &doc[6..9], + &doc[9..11], ) } +/// Gera e retorna um CNH aleatório sem máscara. +/// +/// ## Exemplo +/// ``` +/// use brado::cnh; +/// +/// let result = cnh::generate(); // "84718735264" +/// assert!(cnh::is_bare(&result)); // true +/// ``` pub fn generate() -> String { let mut cnh: Vec = random_digit_vector(9); let (d10, dsc): (u16, u16) = generate_first_digit(&cnh); @@ -111,6 +216,15 @@ pub fn generate() -> String { .join("") } +/// Gera e retorna um CNH aleatório com máscara. +/// +/// ## Exemplo +/// ``` +/// use brado::cnh; +/// +/// let result = cnh::generate_masked(); // "847 187 352 64" +/// assert!(cnh::is_masked(&result)); // true +/// ``` pub fn generate_masked() -> String { mask(&generate()) } diff --git a/brado/src/cnpj.rs b/brado/src/cnpj.rs index 96a416a..297aaa4 100644 --- a/brado/src/cnpj.rs +++ b/brado/src/cnpj.rs @@ -1,13 +1,42 @@ -use crate::common::{get_digits, get_symbols, random_digit_vector}; +//! Utilitários para validação de CNPJ. -pub fn validate(cnpj: &str) -> bool { - let size: usize = cnpj.chars().count(); +use crate::common::{get_digits, get_symbols, random_digit_vector}; - if size != 14 && !is_masked(cnpj) { +/// Realiza validação de CNPJ, máscarado ou não. +/// Retorna `true` se o argumento `doc` for um CNPJ válido, +/// caso contrário, retorna `false`. +/// +/// ## Exemplos +/// +/// CNPJs válidos: +/// ``` +/// use brado::cnpj; +/// +/// let result = cnpj::validate("05200851000100"); // true +/// assert!(result); +/// +/// let result = cnpj::validate("05.200.851/0001-00"); // true +/// assert!(result); +/// ``` +/// +/// CNPJs inválidos: +/// ``` +/// use brado::cnpj; +/// +/// let result = cnpj::validate("05200851000101"); // false +/// assert!(!result); +/// +/// let result = cnpj::validate("05.200.851/0001-01"); // false +/// assert!(!result); +/// ``` +pub fn validate(doc: &str) -> bool { + let size: usize = doc.chars().count(); + + if size != 14 && !is_masked(doc) { return false; } - let digits: Vec = get_digits(cnpj); + let digits: Vec = get_digits(doc); if digits.len() != 14 { return false; @@ -24,29 +53,29 @@ pub fn validate(cnpj: &str) -> bool { (d13, d14) == (digits[12], digits[13]) } -fn generate_digits(cnpj_slice: &[u16]) -> (u16, u16) { - let mut cnpj_slice: Vec = cnpj_slice.to_vec(); +fn generate_digits(doc_slice: &[u16]) -> (u16, u16) { + let mut doc_slice: Vec = doc_slice.to_vec(); let weights: Vec = vec![5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2]; - let d13: u16 = generate_digit(&cnpj_slice, 12, weights); + let d13: u16 = generate_digit(&doc_slice, 12, weights); - cnpj_slice.push(d13); + doc_slice.push(d13); let weights: Vec = vec![6, 5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2]; - let d14: u16 = generate_digit(&cnpj_slice, 13, weights); + let d14: u16 = generate_digit(&doc_slice, 13, weights); (d13, d14) } fn generate_digit( - cnpj_slice: &[u16], + doc_slice: &[u16], max: usize, weights: Vec, ) -> u16 { let mut sum: u16 = 0; for i in 0..max { - sum += cnpj_slice[i] * weights[i]; + sum += doc_slice[i] * weights[i]; } sum %= 11; @@ -60,14 +89,61 @@ fn generate_digit( sum } -pub fn is_bare(cnpj: &str) -> bool { - cnpj.chars().count() == 14 && get_digits(cnpj).len() == 14 +/// Verifica se o argumento `doc` pode ser um CNPJ sem símbolos. +/// Se for, retorna `true`, caso contrário, retorna `false`. +/// +/// ## Exemplos +/// +/// CNPJs válidos: +/// ``` +/// use brado::cnpj; +/// +/// let result = cnpj::is_bare("05200851000100"); // true +/// assert!(result); +/// +/// let result = cnpj::is_bare("05.200.851/0001-00"); // false +/// assert!(!result); +/// ``` +/// +/// CNPJs inválidos: +/// ``` +/// use brado::cnpj; +/// +/// let result = cnpj::is_bare("05200851000101"); // true +/// assert!(result); +/// ``` +pub fn is_bare(doc: &str) -> bool { + doc.chars().count() == 14 && get_digits(doc).len() == 14 } -pub fn is_masked(cnpj: &str) -> bool { - let symbols: Vec<(usize, char)> = get_symbols(cnpj); - - if symbols.len() != 4 { +/// Verifica se o argumento `doc` pode ser um CNPJ com símbolos. +/// Se for, retorna `true`, caso contrário, retorna `false`. +/// +/// ## Exemplos +/// +/// CNPJs válidos: +/// ``` +/// use brado::cnpj; +/// +/// let result = cnpj::is_masked("05.200.851/0001-00"); // true +/// assert!(result); +/// +/// let result = cnpj::is_masked("05200851000100"); // false +/// assert!(!result); +/// ``` +/// +/// CNPJs inválidos: +/// ``` +/// use brado::cnpj; +/// +/// let result = cnpj::is_masked("05.200.851/0001-01"); // true +/// assert!(result); +/// ``` +pub fn is_masked(doc: &str) -> bool { + let symbols: Vec<(usize, char)> = get_symbols(doc); + let digits: Vec = get_digits(doc); + + if symbols.len() != 4 || digits.len() != 14 { return false; } @@ -77,21 +153,50 @@ pub fn is_masked(cnpj: &str) -> bool { && symbols[3] == (15, '-') } -pub fn mask(cnpj: &str) -> String { - if !is_bare(cnpj) { +/// Aplica máscara de CNPJ no argumento `doc` e retorna resultado. +/// O argumento deve ser uma string sem símbolos, caso contrário, +/// deve lançar erro. +/// +/// ## Exemplos +/// +/// Documento de 14 dígitos sem máscara: +/// ``` +/// use brado::cnpj; +/// +/// let result = cnpj::mask("05200851000100"); // "05.200.851/0001-00" +/// assert!(cnpj::is_masked(&result)); // true +/// ``` +/// +/// Documento de 14 dígitos com máscara: +/// ```should_panic +/// use brado::cnpj; +/// +/// cnpj::mask("05.200.851/0001-00"); // panic! +/// ``` +pub fn mask(doc: &str) -> String { + if !is_bare(doc) { panic!("The given string cannot be masked as CNPJ!") } format!( "{}.{}.{}/{}-{}", - &cnpj[0..2], - &cnpj[2..5], - &cnpj[5..8], - &cnpj[8..12], - &cnpj[12..14], + &doc[0..2], + &doc[2..5], + &doc[5..8], + &doc[8..12], + &doc[12..14], ) } +/// Gera e retorna um CNPJ aleatório sem máscara. +/// +/// ## Exemplo +/// ``` +/// use brado::cnpj; +/// +/// let result = cnpj::generate(); // "05200851000100" +/// assert!(cnpj::is_bare(&result)); // true +/// ``` pub fn generate() -> String { let cnpj: Vec = random_digit_vector(12); let (d13, d14): (u16, u16) = generate_digits(&cnpj); @@ -103,6 +208,15 @@ pub fn generate() -> String { .join("") } +/// Gera e retorna um CNPJ aleatório com máscara. +/// +/// ## Exemplo +/// ``` +/// use brado::cnpj; +/// +/// let result = cnpj::generate_masked(); // "05.200.851/0001-00" +/// assert!(cnpj::is_masked(&result)); // true +/// ``` pub fn generate_masked() -> String { mask(&generate()) } diff --git a/brado/src/common.rs b/brado/src/common.rs index e58a974..40d7500 100644 --- a/brado/src/common.rs +++ b/brado/src/common.rs @@ -1,23 +1,63 @@ +//! Funções comuns utilizadas na validação de docos. + use rand::Rng; use std::collections::HashSet; const RADIX: u32 = 10; +/// Verifica se o vetor de dígitos possui um único numeral. +/// Se possuir, retorna `true`, caso contrário, retorna `false`. +/// +/// ## Exemplo +/// +/// ``` +/// use brado::common::is_repeated; +/// +/// let result = is_repeated(&vec![1, 1, 1]); // true +/// assert!(result); +/// +/// let result = is_repeated(&vec![1, 2, 1]); // false +/// assert!(!result); +/// ``` pub fn is_repeated(digits: &[u16]) -> bool { let a_set: HashSet = HashSet::from_iter(digits.iter().cloned()); a_set.len() == 1 } -pub fn get_digits(document: &str) -> Vec { - document - .chars() +/// Extrai e retorna o vetor dígitos de uma string (`&str`). +/// +/// ## Exemplo +/// +/// ``` +/// use brado::common::get_digits; +/// +/// let result = get_digits("111"); +/// assert_eq!(result, vec![1, 1, 1]); +/// +/// let result = get_digits("121"); +/// assert_eq!(result, vec![1, 2, 1]); +/// ``` +pub fn get_digits(doc: &str) -> Vec { + doc.chars() .filter_map(|c| c.to_digit(RADIX).map(|c| c as u16)) .collect() } -pub fn get_symbols(document: &str) -> Vec<(usize, char)> { - document - .chars() +/// Extrai e retorna o vetor de símbolos de uma string (`&str`). +/// O vetor resultante é um vetor de tuplas de dois elementos: o +/// primeiro representa a posição do símbolo na string e o segundo +/// o próprio símbolo. +/// +/// ## Exemplo +/// +/// ``` +/// use brado::common::get_symbols; +/// +/// let result = get_symbols("1.1-1"); +/// assert_eq!(result, vec![(1, '.'), (3, '-')]); +/// ``` +pub fn get_symbols(doc: &str) -> Vec<(usize, char)> { + doc.chars() .enumerate() .filter_map(|(i, c)| match c.to_digit(RADIX) { Some(_) => None, @@ -26,14 +66,34 @@ pub fn get_symbols(document: &str) -> Vec<(usize, char)> { .collect() } -pub fn unmask(document: &str) -> String { - document - .chars() +/// Remove os símbolos (desmascara) de uma string (`&str`) +/// e retorna a string resultante. +/// +/// ## Exemplo +/// +/// ``` +/// use brado::common::unmask; +/// +/// let result = unmask("1.1-1"); +/// assert_eq!(result, String::from("111")); +/// ``` +pub fn unmask(doc: &str) -> String { + doc.chars() .filter_map(|c| c.to_digit(RADIX).map(|c| c.to_string())) .collect::>() .join("") } +/// Gera e retorna um vetor de dígitos aleatórios com o tamanho `size`. +/// +/// ## Exemplo +/// +/// ``` +/// use brado::common::random_digit_vector; +/// +/// let result = random_digit_vector(10); +/// assert_eq!(result.len(), 10); +/// ``` pub fn random_digit_vector(size: usize) -> Vec { let mut rng = rand::thread_rng(); let mut digits: Vec = vec![]; diff --git a/brado/src/cpf.rs b/brado/src/cpf.rs index 787648d..0f60f94 100644 --- a/brado/src/cpf.rs +++ b/brado/src/cpf.rs @@ -1,15 +1,44 @@ +//! Utilitários para validação de CPF. + use crate::common::{ get_digits, get_symbols, is_repeated, random_digit_vector, }; -pub fn validate(cpf: &str) -> bool { - let size: usize = cpf.chars().count(); - - if size != 11 && !is_masked(cpf) { +/// Realiza validação de CPF, máscarado ou não. +/// Retorna `true` se o argumento `doc` for um CPF válido, +/// caso contrário, retorna `false`. +/// +/// ## Exemplos +/// +/// CPFs válidos: +/// ``` +/// use brado::cpf; +/// +/// let result = cpf::validate("63929247011"); // true +/// assert!(result); +/// +/// let result = cpf::validate("639.292.470-11"); // true +/// assert!(result); +/// ``` +/// +/// CPFs inválidos: +/// ``` +/// use brado::cpf; +/// +/// let result = cpf::validate("63929247010"); // false +/// assert!(!result); +/// +/// let result = cpf::validate("639.292.470-10"); // false +/// assert!(!result); +/// ``` +pub fn validate(doc: &str) -> bool { + let size: usize = doc.chars().count(); + + if size != 11 && !is_masked(doc) { return false; } - let digits: Vec = get_digits(cpf); + let digits: Vec = get_digits(doc); if digits.len() != 11 || is_repeated(&digits) { return false; @@ -20,22 +49,22 @@ pub fn validate(cpf: &str) -> bool { (d10, d11) == (digits[9], digits[10]) } -fn generate_digits(cpf_slice: &[u16]) -> (u16, u16) { - let d10: u16 = generate_digit(cpf_slice, 10); - let d11: u16 = generate_digit(cpf_slice, 11); +fn generate_digits(doc_slice: &[u16]) -> (u16, u16) { + let d10: u16 = generate_digit(doc_slice, 10); + let d11: u16 = generate_digit(doc_slice, 11); (d10, d11) } fn generate_digit( - cpf_slice: &[u16], + doc_slice: &[u16], max: u16, ) -> u16 { let mut sum: u16 = 0; for i in (2..=max).rev() { let idx: usize = (max - i) as usize; - sum += cpf_slice[idx] * i; + sum += doc_slice[idx] * i; } sum = (sum * 10) % 11; @@ -47,34 +76,110 @@ fn generate_digit( sum } -pub fn is_bare(cpf: &str) -> bool { - cpf.chars().count() == 11 && get_digits(cpf).len() == 11 +/// Verifica se o argumento `doc` pode ser um CPF sem símbolos. +/// Se for, retorna `true`, caso contrário, retorna `false`. +/// +/// ## Exemplos +/// +/// CPFs válidos: +/// ``` +/// use brado::cpf; +/// +/// let result = cpf::is_bare("63929247011"); // true +/// assert!(result); +/// +/// let result = cpf::is_bare("639.292.470-11"); // false +/// assert!(!result); +/// ``` +/// +/// CPFs inválidos: +/// ``` +/// use brado::cpf; +/// +/// let result = cpf::is_bare("63929247010"); // true +/// assert!(result); +/// ``` +pub fn is_bare(doc: &str) -> bool { + doc.chars().count() == 11 && get_digits(doc).len() == 11 } -pub fn is_masked(cpf: &str) -> bool { - let symbols: Vec<(usize, char)> = get_symbols(cpf); - - if symbols.len() != 3 { +/// Verifica se o argumento `doc` pode ser um CPF com símbolos. +/// Se for, retorna `true`, caso contrário, retorna `false`. +/// +/// ## Exemplos +/// +/// CPFs válidos: +/// ``` +/// use brado::cpf; +/// +/// let result = cpf::is_masked("639.292.470-11"); // true +/// assert!(result); +/// +/// let result = cpf::is_masked("63929247011"); // false +/// assert!(!result); +/// ``` +/// +/// CPFs inválidos: +/// ``` +/// use brado::cpf; +/// +/// let result = cpf::is_masked("639.292.470-10"); // true +/// assert!(result); +/// ``` +pub fn is_masked(doc: &str) -> bool { + let symbols: Vec<(usize, char)> = get_symbols(doc); + let digits: Vec = get_digits(doc); + + if symbols.len() != 3 || digits.len() != 11 { return false; } symbols[0] == (3, '.') && symbols[1] == (7, '.') && symbols[2] == (11, '-') } -pub fn mask(cpf: &str) -> String { - if !is_bare(cpf) { +/// Aplica máscara de CPF no argumento `doc` e retorna resultado. +/// O argumento deve ser uma string sem símbolos, caso contrário, +/// deve lançar erro. +/// +/// ## Exemplos +/// +/// Documento de 11 dígitos sem máscara: +/// ``` +/// use brado::cpf; +/// +/// let result = cpf::mask("63929247011"); // "639.292.470-11" +/// assert!(cpf::is_masked(&result)); // true +/// ``` +/// +/// Documento de 11 dígitos com máscara: +/// ```should_panic +/// use brado::cpf; +/// +/// cpf::mask("639.292.470-11"); // panic! +/// ``` +pub fn mask(doc: &str) -> String { + if !is_bare(doc) { panic!("The given string cannot be masked as CPF!") } format!( "{}.{}.{}-{}", - &cpf[0..3], - &cpf[3..6], - &cpf[6..9], - &cpf[9..11], + &doc[0..3], + &doc[3..6], + &doc[6..9], + &doc[9..11], ) } +/// Gera e retorna um CPF aleatório sem máscara. +/// +/// ## Exemplo +/// ``` +/// use brado::cpf; +/// +/// let result = cpf::generate(); // "63929247011" +/// assert!(cpf::is_bare(&result)); // true +/// ``` pub fn generate() -> String { let mut cpf: Vec = random_digit_vector(9); cpf.push(generate_digit(&cpf, 10)); @@ -86,6 +191,15 @@ pub fn generate() -> String { .join("") } +/// Gera e retorna um CPF aleatório com máscara. +/// +/// ## Exemplo +/// ``` +/// use brado::cpf; +/// +/// let result = cpf::generate_masked(); // "639.292.470-11" +/// assert!(cpf::is_masked(&result)); // true +/// ``` pub fn generate_masked() -> String { mask(&generate()) } diff --git a/brado/src/docs.rs b/brado/src/docs.rs index a1542ef..75055b7 100644 --- a/brado/src/docs.rs +++ b/brado/src/docs.rs @@ -1,15 +1,98 @@ +//! Utilitários para validação de documentos. + use crate::cnh; use crate::cnpj; use crate::cpf; -pub fn is_cpf(document: &str) -> bool { - cpf::validate(document) +/// Verifica se um documento `doc` é um CPF, máscarado ou não. +/// Retorna `true` se o argumento `doc` for um CPF válido, +/// caso contrário, retorna `false`. +/// +/// ## Exemplos +/// +/// CPFs válidos: +/// ``` +/// use brado::docs; +/// +/// let result = docs::is_cpf("63929247011"); // true +/// assert!(result); +/// +/// let result = docs::is_cpf("639.292.470-11"); // true +/// assert!(result); +/// ``` +/// +/// CPFs inválidos: +/// ``` +/// use brado::docs; +/// +/// let result = docs::is_cpf("63929247010"); // false +/// assert!(!result); +/// +/// let result = docs::is_cpf("639.292.470-10"); // false +/// assert!(!result); +/// ``` +pub fn is_cpf(doc: &str) -> bool { + cpf::validate(doc) } -pub fn is_cnpj(document: &str) -> bool { - cnpj::validate(document) +/// Verifica se um documento `doc` é um CNPJ, máscarado ou não. +/// Retorna `true` se o argumento `doc` for um CNPJ válido, +/// caso contrário, retorna `false`. +/// +/// ## Exemplos +/// +/// CNPJs válidos: +/// ``` +/// use brado::docs; +/// +/// let result = docs::is_cnpj("05200851000100"); // true +/// assert!(result); +/// +/// let result = docs::is_cnpj("05.200.851/0001-00"); // true +/// assert!(result); +/// ``` +/// +/// CNPJs inválidos: +/// ``` +/// use brado::docs; +/// +/// let result = docs::is_cnpj("05200851000101"); // false +/// assert!(!result); +/// +/// let result = docs::is_cnpj("05.200.851/0001-01"); // false +/// assert!(!result); +/// ``` +pub fn is_cnpj(doc: &str) -> bool { + cnpj::validate(doc) } -pub fn is_cnh(document: &str) -> bool { - cnh::validate(document) +/// Verifica se um documento `doc` é uma CNH, máscarado ou não. +/// Retorna `true` se o argumento `doc` for uma CNH válido, +/// caso contrário, retorna `false`. +/// +/// ## Exemplos +/// +/// CNHs válidos: +/// ``` +/// use brado::docs; +/// +/// let result = docs::is_cnh("84718735264"); // true +/// assert!(result); +/// +/// let result = docs::is_cnh("847 187 352 64"); // true +/// assert!(result); +/// ``` +/// +/// CNHs inválidos: +/// ``` +/// use brado::docs; +/// +/// let result = docs::is_cnh("84718735265"); // false +/// assert!(!result); +/// +/// let result = docs::is_cnh("847 187 352 65"); // false +/// assert!(!result); +/// ``` +pub fn is_cnh(doc: &str) -> bool { + cnh::validate(doc) } diff --git a/brado/src/lib.rs b/brado/src/lib.rs index aa19a2a..c3d62a7 100644 --- a/brado/src/lib.rs +++ b/brado/src/lib.rs @@ -1,11 +1,25 @@ -//! # brado documentation +// Copyright 2024 Developers of the Brado project. +// +// Licensed under the MIT license . +// This file may not be copied, modified, or distributed except according to +// those terms. + +//! Utilitários para validação de documentos brasileiros //! -//! brado is a brazilian docs validator. +//! Brado fornece funções para identificação, validação e geração de documentos +//! brasileiros. O nome desta biblioteca (Brado) é um acronimo de BRAzilian +//! DOcs validator (validador de DOcumentos BRAsileiros). +//! +//! # Guia Rápido +//! +//! Para utilizar a biblioteca, basta importá-la no arquivo (`use brado`), +//! escolher o módulo correspondente ao documento e chamar a função desejada. +//! Por exemplo, para validar um CPF, basta utilizar o código a seguir: //! -//! ## Example //! ``` -//! use brado::cpf; -//! cpf::validate("639.292.470-11"); +//! use brado; +//! let result = brado::cpf::validate("639.292.470-11"); +//! assert!(result); //! ``` pub mod cnh; pub mod cnpj; diff --git a/brado/tests/cnpj.rs b/brado/tests/cnpj.rs index c08cf3e..f7c7936 100644 --- a/brado/tests/cnpj.rs +++ b/brado/tests/cnpj.rs @@ -50,6 +50,12 @@ mod cnpj_tests { assert_eq!(brado::cnpj::is_bare(masked_cnpj), false); } + #[test] + fn cnpj_is_bare_3_other_document() { + let bare_document: &str = "052008510001001"; + assert_eq!(brado::cnpj::is_bare(bare_document), false); + } + #[test] fn cnpj_is_masked_1_masked_cnpj() { let masked_cnpj: &str = "05.200.851/0001-00"; @@ -62,6 +68,18 @@ mod cnpj_tests { assert_eq!(brado::cnpj::is_masked(bare_cnpj), false); } + #[test] + fn cnpj_is_masked_3_other_document() { + let masked_document: &str = "0.520.085/1000-100"; + assert_eq!(brado::cnpj::is_masked(masked_document), false); + } + + #[test] + fn cnpj_is_masked_4_other_document() { + let masked_document: &str = "00.520.085/1000-100"; + assert_eq!(brado::cnpj::is_masked(masked_document), false); + } + #[test] fn cnpj_mask_1_bare_cnpj() { let bare_cnpj: &str = "05200851000100"; @@ -93,7 +111,7 @@ mod cnpj_tests { } #[test] - fn cpf_generate_masked_1() { + fn cnpj_generate_masked_1() { let cnpj = brado::cnpj::generate_masked(); assert_eq!(brado::cnpj::validate(&cnpj), true); assert_eq!(brado::cnpj::is_masked(&cnpj), true); diff --git a/brado/tests/cpf.rs b/brado/tests/cpf.rs index a3216b2..8e28bea 100644 --- a/brado/tests/cpf.rs +++ b/brado/tests/cpf.rs @@ -58,6 +58,12 @@ mod cpf_tests { assert_eq!(brado::cpf::is_bare(masked_cpf), false); } + #[test] + fn cpf_is_bare_3_other_document() { + let bare_document: &str = "639292470110"; + assert_eq!(brado::cpf::is_bare(bare_document), false); + } + #[test] fn cpf_is_masked_1_masked_cpf() { let masked_cpf: &str = "639.292.470-11"; @@ -70,6 +76,18 @@ mod cpf_tests { assert_eq!(brado::cpf::is_masked(bare_cpf), false); } + #[test] + fn cpf_is_masked_3_other_document() { + let masked_document: &str = "6392.292.470-11"; + assert_eq!(brado::cpf::is_masked(masked_document), false); + } + + #[test] + fn cpf_is_masked_4_other_document() { + let masked_document: &str = "639.292.470-110"; + assert_eq!(brado::cpf::is_masked(masked_document), false); + } + #[test] fn cpf_mask_1_bare_cpf() { let bare_cpf: &str = "63929247011";