Validar CPF em tempo real com Rust combina verificação algorítmica dos dígitos verificadores — feita localmente, sem custo de rede — com uma consulta à API REST da CPFHub.io que retorna nome completo, gênero e data de nascimento em ~900ms. O sistema de tipos de Rust garante que todos os cenários de erro sejam tratados em tempo de compilação, resultando em integrações robustas e sem surpresas em produção. A API nunca retorna HTTP 429 ao ultrapassar o limite: consultas adicionais são cobradas a R$0,15 cada.
Introdução
Validar um CPF vai além de verificar seus dígitos verificadores. A validação em tempo real combina a checagem algorítmica local com a confirmação dos dados através de uma API externa. Rust, com seu sistema de tipos e performance, é uma escolha excelente para implementar esse tipo de validação.
Validação algorítmica do CPF
O CPF brasileiro possui dois dígitos verificadores calculados a partir dos nove primeiros dígitos. Implemente essa validação de forma idiomática em Rust:
pub fn validar_digitos_cpf(cpf: &str) -> bool {
let digitos: Vec<u32> = cpf.chars()
.filter(|c| c.is_ascii_digit())
.filter_map(|c| c.to_digit(10))
.collect();
if digitos.len() != 11 {
return false;
}
// Rejeitar CPFs com todos os dígitos iguais
if digitos.iter().all(|&d| d == digitos[0]) {
return false;
}
// Primeiro dígito verificador
let soma: u32 = digitos.iter()
.take(9)
.enumerate()
.map(|(i, &d)| d * (10 - i as u32))
.sum();
let d1 = if soma % 11 < 2 { 0 } else { 11 - (soma % 11) };
// Segundo dígito verificador
let soma: u32 = digitos.iter()
.take(10)
.enumerate()
.map(|(i, &d)| d * (11 - i as u32))
.sum();
let d2 = if soma % 11 < 2 { 0 } else { 11 - (soma % 11) };
digitos[9] == d1 && digitos[10] == d2
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_cpf_invalido_digitos_iguais() {
assert!(!validar_digitos_cpf("11111111111"));
}
#[test]
fn test_cpf_invalido_tamanho() {
assert!(!validar_digitos_cpf("1234"));
}
}
Estrutura de validação em camadas
Organize a validação em etapas progressivas para otimizar o uso de recursos:
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize)]
pub struct ValidacaoResult {
pub formato_valido: bool,
pub encontrado_na_base: bool,
pub nome: Option<String>,
pub data_nascimento: Option<String>,
pub mensagem: String,
}
#[derive(Debug, Deserialize)]
pub struct CpfResponse {
pub success: bool,
pub data: CpfData,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CpfData {
pub cpf: String,
pub name: String,
pub name_upper: String,
pub gender: String,
pub birth_date: String,
pub day: String,
pub month: String,
pub year: String,
}
| Camada | Operação | Custo |
|---|---|---|
| 1 - Formato | Verificar tamanho e caracteres | Negligível |
| 2 - Dígitos | Calcular dígitos verificadores | Negligível |
| 3 - API | Consultar base de dados externa | ~900ms |
Implementando o validador completo
Combine todas as camadas em uma função assíncrona que retorna o resultado detalhado:
use reqwest::header::{HeaderMap, HeaderValue};
pub struct CpfValidator {
client: reqwest::Client,
api_key: String,
}
impl CpfValidator {
pub fn new(api_key: String) -> Self {
let client = reqwest::Client::builder()
.timeout(std::time::Duration::from_secs(5))
.build()
.expect("Falha ao criar cliente HTTP");
Self { client, api_key }
}
pub async fn validar_completo(&self, cpf: &str) -> ValidacaoResult {
let cpf_limpo: String = cpf.chars()
.filter(|c| c.is_ascii_digit())
.collect();
// Camada 1: Formato
if cpf_limpo.len() != 11 {
return ValidacaoResult {
formato_valido: false,
encontrado_na_base: false,
nome: None,
data_nascimento: None,
mensagem: "CPF deve conter 11 dígitos".to_string(),
};
}
// Camada 2: Dígitos verificadores
if !validar_digitos_cpf(&cpf_limpo) {
return ValidacaoResult {
formato_valido: false,
encontrado_na_base: false,
nome: None,
data_nascimento: None,
mensagem: "Dígitos verificadores inválidos".to_string(),
};
}
// Camada 3: Consulta à API
match self.consultar_api(&cpf_limpo).await {
Ok(response) if response.success => ValidacaoResult {
formato_valido: true,
encontrado_na_base: true,
nome: Some(response.data.name),
data_nascimento: Some(response.data.birth_date),
mensagem: "CPF válido e encontrado na base".to_string(),
},
Ok(_) => ValidacaoResult {
formato_valido: true,
encontrado_na_base: false,
nome: None,
data_nascimento: None,
mensagem: "CPF válido, porém não encontrado".to_string(),
},
Err(e) => ValidacaoResult {
formato_valido: true,
encontrado_na_base: false,
nome: None,
data_nascimento: None,
mensagem: format!("Erro ao consultar API: {}", e),
},
}
}
async fn consultar_api(&self, cpf: &str) -> Result<CpfResponse, String> {
let mut headers = HeaderMap::new();
headers.insert(
"x-api-key",
HeaderValue::from_str(&self.api_key)
.map_err(|e| e.to_string())?,
);
let url = format!("https://api.cpfhub.io/cpf/{}", cpf);
let response = self.client
.get(&url)
.headers(headers)
.send()
.await
.map_err(|e| e.to_string())?;
response.json::<CpfResponse>()
.await
.map_err(|e| e.to_string())
}
}
Utilizando o validador
Integre o validador no seu programa principal:
#[tokio::main]
async fn main() {
let api_key = std::env::var("CPFHUB_API_KEY")
.expect("Defina CPFHUB_API_KEY no ambiente");
let validator = CpfValidator::new(api_key);
let cpfs = vec!["12345678900", "11111111111", "98765432100"];
for cpf in cpfs {
let resultado = validator.validar_completo(cpf).await;
println!("CPF: {} -> {}", cpf, resultado.mensagem);
if let Some(nome) = &resultado.nome {
println!(" Nome: {}", nome);
}
if let Some(nascimento) = &resultado.data_nascimento {
println!(" Nascimento: {}", nascimento);
}
println!("---");
}
}
Para referência sobre o modelo assíncrono de Rust, consulte a documentação oficial do Tokio e a crate reqwest.
Perguntas frequentes
O que acontece quando o volume de consultas ultrapassa o limite do plano em Rust?
A API da CPFHub.io nunca retorna HTTP 429 nem interrompe requisições. Quando o limite mensal é superado, cada consulta adicional é cobrada a R$0,15. Isso significa que seu código Rust não precisa implementar lógica de retry por bloqueio de quota — o fluxo continua normalmente, e você acompanha o consumo em app.cpfhub.io/settings/billing.
Por que usar validação em camadas em vez de consultar a API diretamente?
A camada algorítmica local (formato e dígitos verificadores) rejeita CPFs matematicamente inválidos sem nenhuma chamada de rede, reduzindo custos e latência. Apenas CPFs com dígitos válidos chegam à camada 3 (API), que confirma existência e retorna os dados cadastrais.
Como tratar erros de timeout na consulta à API em Rust?
Configure o reqwest::Client com .timeout(Duration::from_secs(5)) como mostrado no exemplo. O tipo Result<CpfResponse, String> garante que erros de rede sejam tratados explicitamente — o compilador de Rust não deixa ignorar o caso de erro, o que torna a integração mais robusta.
Qual crate usar para deserializar a resposta JSON da CPFHub.io em Rust?
Use serde com serde_json (ou o suporte JSON integrado do reqwest via feature json). Os campos da resposta seguem camelCase — use #[serde(rename_all = "camelCase")] na struct CpfData para mapear automaticamente birthDate, nameUpper etc., como demonstrado neste artigo.
Conclusão
A validação de CPF em tempo real com Rust combina a eficiência da verificação algorítmica local com a riqueza de dados da API da CPFHub.io. O sistema de tipos de Rust garante que todos os cenários de erro sejam tratados explicitamente, resultando em software robusto e confiável. A API responde em ~900ms e nunca bloqueia por volume — consultas além do plano são simplesmente cobradas como excedente.
Cadastre-se em cpfhub.io — 50 consultas mensais gratuitas, sem cartão de crédito — e comece a validar CPFs na sua aplicação Rust hoje mesmo.
CPFHub.io
Pronto para integrar a API?
50 consultas gratuitas para testar agora. Sem cartão de crédito. Acesso imediato à documentação.
Sobre a redação
Redação CPFHub.io
Time editorial especializado em APIs de CPF, identidade digital e compliance no mercado brasileiro. Produzimos guias técnicos, análises regulatórias e tutoriais sobre LGPD e KYC para desenvolvedores e líderes de produto.



