# Como criar um CLI de validação de CPF em Rust

> Construa uma ferramenta de linha de comando em Rust para validar e consultar CPFs usando a crate clap e a API do CPFHub.

**Publicado:** 06/11/2024
**Autor:** Redação CPFHub.io
**URL:** https://cpfhub.io/blog/como-criar-cli-validacao-cpf-rust

---


Rust, combinado com a crate `clap`, permite criar uma ferramenta de linha de comando para validar e consultar CPFs com parsing de argumentos, subcomandos e documentação automática gerada a partir do código. Este guia mostra como construir um CLI com três subcomandos: validação local de dígitos verificadores, consulta à API da CPFHub.io e processamento em lote a partir de arquivo.

## Introdução

Ferramentas de linha de comando são extremamente úteis para automação e integração com scripts. Rust, com a crate `clap`, oferece uma forma elegante de construir CLIs com parsing de argumentos, subcomandos e documentação automática. A documentação oficial da linguagem em [doc.rust-lang.org](https://doc.rust-lang.org/book/) detalha os padrões de projeto assíncronos usados neste tutorial.

---

## Configurando o projeto

Crie o projeto e adicione as dependências ao `Cargo.toml`:

```rust
// Cargo.toml
[package]
name = "cpf-cli"
version = "0.1.0"
edition = "2021"

[dependencies]
clap = { version = "4", features = ["derive"] }
reqwest = { version = "0.12", features = ["json"] }
serde = { version = "1.0", features = ["derive"] }
tokio = { version = "1", features = ["full"] }
colored = "2"
```

| Crate | Finalidade |
|---|---|
| `clap` | Parsing de argumentos e subcomandos |
| `reqwest` | Requisições HTTP à API |
| `serde` | Desserialização JSON |
| `colored` | Saída colorida no terminal |

---

## Definindo a interface do CLI

Utilize os derives do `clap` para declarar os argumentos e subcomandos:

```rust
use clap::{Parser, Subcommand};

#[derive(Parser)]
#[command(name = "cpf-cli")]
#[command(about = "Ferramenta de validação e consulta de CPF")]
#[command(version = "1.0")]
struct Cli {
 #[command(subcommand)]
 command: Commands,
}

#[derive(Subcommand)]
enum Commands {
 /// Validar os dígitos verificadores de um CPF
 Validar {
 /// O CPF a ser validado (apenas dígitos ou formatado)
 cpf: String,
 },
 /// Consultar dados de um CPF na API do CPFHub
 Consultar {
 /// O CPF a ser consultado
 cpf: String,

 /// Chave de API do CPFHub (ou use CPFHUB_API_KEY)
 #[arg(short, long, env = "CPFHUB_API_KEY")]
 api_key: String,
 },
 /// Processar um lote de CPFs a partir de um arquivo
 Lote {
 /// Caminho do arquivo com CPFs (um por linha)
 arquivo: String,

 /// Chave de API do CPFHub
 #[arg(short, long, env = "CPFHUB_API_KEY")]
 api_key: String,
 },
}
```

---

## Implementando a validação local

O subcomando `validar` verifica os dígitos verificadores sem chamar a API:

```rust
use colored::*;

fn validar_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 || digitos.iter().all(|&d| d == digitos[0]) {
 return false;
 }

 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) };

 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
}

fn executar_validacao(cpf: &str) {
 let cpf_limpo: String = cpf.chars().filter(|c| c.is_ascii_digit()).collect();

 if cpf_limpo.len() != 11 {
 println!("{} CPF deve conter 11 dígitos (recebido: {})",
 "ERRO:".red().bold(), cpf_limpo.len());
 return;
 }

 if validar_cpf(&cpf_limpo) {
 println!("{} CPF {} possui dígitos verificadores válidos",
 "VALIDO:".green().bold(), cpf);
 } else {
 println!("{} CPF {} possui dígitos verificadores inválidos",
 "INVALIDO:".red().bold(), cpf);
 }
}
```

---

## Implementando a consulta à API

O subcomando `consultar` chama a API do CPFHub e exibe os dados formatados:

```rust
use serde::Deserialize;
use reqwest::header::{HeaderMap, HeaderValue};

#[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,
}

async fn executar_consulta(cpf: &str, api_key: &str) {
 let mut headers = HeaderMap::new();
 headers.insert("x-api-key", HeaderValue::from_str(api_key).unwrap());

 let client = reqwest::Client::new();
 let url = format!("https://api.cpfhub.io/cpf/{}", cpf);

 match client.get(&url).headers(headers).send().await {
 Ok(response) if response.status().is_success() => {
 match response.json::<CpfResponse>().await {
 Ok(dados) if dados.success => {
 println!("{}", "CPF encontrado!".green().bold());
 println!(" {}: {}", "Nome".bold(), dados.data.name);
 println!(" {}: {}", "CPF".bold(), dados.data.cpf);
 println!(" {}: {}", "Nascimento".bold(), dados.data.birth_date);
 println!(" {}: {}", "Gênero".bold(), dados.data.gender);
 }
 _ => println!("{} CPF não encontrado na base", "AVISO:".yellow().bold()),
 }
 }
 Ok(response) => {
 println!("{} API retornou status {}", "ERRO:".red().bold(), response.status());
 }
 Err(e) => {
 println!("{} {}", "ERRO:".red().bold(), e);
 }
 }
}
```

---

## Montando o programa principal

Conecte todos os subcomandos no ponto de entrada da aplicação:

```rust
#[tokio::main]
async fn main() {
 let cli = Cli::parse();

 match cli.command {
 Commands::Validar { cpf } => {
 executar_validacao(&cpf);
 }
 Commands::Consultar { cpf, api_key } => {
 executar_consulta(&cpf, &api_key).await;
 }
 Commands::Lote { arquivo, api_key } => {
 let conteudo = std::fs::read_to_string(&arquivo)
 .expect("Não foi possível ler o arquivo");

 let cpfs: Vec<&str> = conteudo.lines()
 .filter(|l| !l.trim().is_empty())
 .collect();

 println!("Processando {} CPFs...\n", cpfs.len());

 for cpf in cpfs {
 executar_consulta(cpf.trim(), &api_key).await;
 println!("---");
 }
 }
 }
}
```

---

## Perguntas frequentes

### Por que usar Rust para um CLI de validação de CPF?
Rust gera binários nativos sem dependência de runtime, com startup quase instantânea e consumo mínimo de memória. Para scripts de automação e pipelines CI/CD que processam grandes volumes de CPFs, isso representa vantagem real frente a Python ou Node.js, que exigem interpretador instalado.

### Como armazenar a chave de API com segurança no CLI?
A forma recomendada é usar a variável de ambiente `CPFHUB_API_KEY`, como mostrado no exemplo com `#[arg(env = "CPFHUB_API_KEY")]`. Isso evita que a chave apareça no histórico do shell ou em logs de processo. Nunca passe a chave como argumento posicional em scripts automatizados.

### A API bloqueia requisições quando o limite mensal é atingido?
Não. A API da CPFHub.io não retorna HTTP 429 nem bloqueia consultas ao atingir o limite do plano gratuito. Ela cobra R$0,15 por consulta excedente e continua respondendo normalmente. Para volumes maiores, o plano Pro oferece 1.000 consultas mensais por R$149.

### Como processar arquivos com milhares de CPFs sem sobrecarregar a API?
Adicione um `sleep` entre as chamadas do subcomando `lote` para respeitar o rate limit do plano (1 req/2s no gratuito, 1 req/s no Pro). Use `tokio::time::sleep(Duration::from_millis(500))` após cada chamada no loop de processamento.

### Leia também

- [Como consumir a API de CPF em Rust usando reqwest](https://cpfhub.io/blog/como-consumir-api-cpf-rust-reqwest)
- [SLA de API de CPF: níveis de disponibilidade e o que exigir do seu provedor](https://cpfhub.io/blog/sla-api-cpf-niveis-disponibilidade)
- [API de CPF grátis para desenvolvedores: como começar em 5 minutos](https://cpfhub.io/blog/api-cpf-gratis-desenvolvedores-comecar-5-minutos)
- [10 erros mais comuns ao integrar uma API de CPF e como evitá-los](https://cpfhub.io/blog/10-erros-mais-comuns-ao-integrar-uma-api-de-cpf)

---

## Conclusão

Criar um CLI de validação de CPF em Rust com `clap` resulta em uma ferramenta rápida, com interface profissional e documentação automática. A combinação de subcomandos, variáveis de ambiente e processamento em lote atende tanto uso interativo quanto automação.

Cadastre-se em [cpfhub.io](https://www.cpfhub.io/) — 50 consultas mensais gratuitas, sem cartão de crédito — e use o CLI com dados reais para automatizar a validação de CPF nos seus scripts e pipelines.

