Validar endereços de email é uma das tarefas mais comuns em aplicações web e CLIs. Existem diferentes níveis de validação, desde uma verificação simples de formato até conformidade com a RFC 5322. Nesta receita, você vai aprender três abordagens: regex, validação manual e a crate validator.
Dependências
[package]
name = "receita-validar-email"
version = "0.1.0"
edition = "2021"
[dependencies]
regex = "1"
validator = { version = "0.19", features = ["derive"] }
Código Completo
use regex::Regex;
use std::sync::LazyLock;
use validator::Validate;
// =============================================
// 1. Validação simples com regex
// =============================================
static RE_EMAIL: LazyLock<Regex> = LazyLock::new(|| {
Regex::new(
r"(?i)^[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}$"
).unwrap()
});
fn validar_email_regex(email: &str) -> bool {
RE_EMAIL.is_match(email.trim())
}
// =============================================
// 2. Validação manual (sem dependências)
// =============================================
#[derive(Debug)]
enum EmailErro {
Vazio,
SemArroba,
MultiploArrobas,
UsuarioVazio,
DominioVazio,
DominioSemPonto,
CaracterInvalido(char),
MuitoLongo,
}
impl std::fmt::Display for EmailErro {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
EmailErro::Vazio => write!(f, "Email vazio"),
EmailErro::SemArroba => write!(f, "Email não contém @"),
EmailErro::MultiploArrobas => write!(f, "Email contém mais de um @"),
EmailErro::UsuarioVazio => write!(f, "Parte do usuário está vazia"),
EmailErro::DominioVazio => write!(f, "Domínio está vazio"),
EmailErro::DominioSemPonto => write!(f, "Domínio não contém ponto"),
EmailErro::CaracterInvalido(c) => write!(f, "Caractere inválido: '{}'", c),
EmailErro::MuitoLongo => write!(f, "Email excede 254 caracteres"),
}
}
}
fn validar_email_manual(email: &str) -> Result<(), EmailErro> {
let email = email.trim();
if email.is_empty() {
return Err(EmailErro::Vazio);
}
if email.len() > 254 {
return Err(EmailErro::MuitoLongo);
}
// Verificar quantidade de @
let arrobas = email.chars().filter(|&c| c == '@').count();
if arrobas == 0 {
return Err(EmailErro::SemArroba);
}
if arrobas > 1 {
return Err(EmailErro::MultiploArrobas);
}
// Separar usuário e domínio
let partes: Vec<&str> = email.splitn(2, '@').collect();
let usuario = partes[0];
let dominio = partes[1];
if usuario.is_empty() {
return Err(EmailErro::UsuarioVazio);
}
if dominio.is_empty() {
return Err(EmailErro::DominioVazio);
}
if !dominio.contains('.') {
return Err(EmailErro::DominioSemPonto);
}
// Verificar caracteres no usuário
let chars_validos = |c: char| {
c.is_alphanumeric() || "._%+-".contains(c)
};
if let Some(invalido) = usuario.chars().find(|c| !chars_validos(*c)) {
return Err(EmailErro::CaracterInvalido(invalido));
}
// Verificar caracteres no domínio
let chars_dominio = |c: char| {
c.is_alphanumeric() || ".-".contains(c)
};
if let Some(invalido) = dominio.chars().find(|c| !chars_dominio(*c)) {
return Err(EmailErro::CaracterInvalido(invalido));
}
// Verificar TLD (pelo menos 2 caracteres)
let tld = dominio.rsplit('.').next().unwrap_or("");
if tld.len() < 2 {
return Err(EmailErro::DominioSemPonto);
}
Ok(())
}
// =============================================
// 3. Validação com a crate validator
// =============================================
#[derive(Debug, Validate)]
struct Cadastro {
#[validate(email(message = "Email inválido"))]
email: String,
#[validate(length(min = 3, max = 50, message = "Nome deve ter 3-50 caracteres"))]
nome: String,
}
fn main() {
let emails_teste = vec![
"usuario@exemplo.com",
"nome.sobrenome@empresa.com.br",
"usuario+tag@gmail.com",
"invalido@",
"@sem-usuario.com",
"sem-arroba.com",
"dois@@arrobas.com",
"",
"a@b.c",
"usuario@dominio.com",
" espacos@email.com ",
];
// =============================================
// Testar regex
// =============================================
println!("=== Validação com Regex ===");
for email in &emails_teste {
let valido = validar_email_regex(email);
let icone = if valido { "OK" } else { "FALHA" };
println!(" [{}] '{}'", icone, email);
}
// =============================================
// Testar validação manual
// =============================================
println!("\n=== Validação Manual (com detalhes) ===");
for email in &emails_teste {
match validar_email_manual(email) {
Ok(()) => println!(" [OK] '{}'", email),
Err(e) => println!(" [FALHA] '{}' -> {}", email, e),
}
}
// =============================================
// Testar crate validator
// =============================================
println!("\n=== Validação com crate validator ===");
let cadastros = vec![
Cadastro {
email: "usuario@exemplo.com".into(),
nome: "Ana Silva".into(),
},
Cadastro {
email: "invalido@".into(),
nome: "Bia".into(),
},
Cadastro {
email: "valido@email.com".into(),
nome: "AB".into(), // muito curto
},
];
for cadastro in &cadastros {
match cadastro.validate() {
Ok(()) => println!(" [OK] {:?}", cadastro),
Err(erros) => println!(" [FALHA] {:?} -> {}", cadastro, erros),
}
}
// =============================================
// Exemplo prático: processar lista de emails
// =============================================
println!("\n=== Processamento de Lista ===");
let entrada = vec![
"ana@empresa.com",
"invalido",
"bia@gmail.com",
"",
"carlos@outlook.com",
"sem-arroba",
];
let (validos, invalidos): (Vec<&str>, Vec<&str>) = entrada
.into_iter()
.partition(|email| validar_email_regex(email));
println!("Válidos ({}):", validos.len());
for email in &validos {
println!(" {}", email);
}
println!("Inválidos ({}):", invalidos.len());
for email in &invalidos {
println!(" '{}'", email);
}
// =============================================
// Normalizar emails
// =============================================
println!("\n=== Normalização ===");
fn normalizar_email(email: &str) -> Option<String> {
let email = email.trim().to_lowercase();
if validar_email_regex(&email) {
Some(email)
} else {
None
}
}
let emails = vec![" USUARIO@GMAIL.COM ", "Ana@Empresa.COM", "invalido"];
for email in &emails {
match normalizar_email(email) {
Some(normalizado) => println!(" '{}' -> '{}'", email, normalizado),
None => println!(" '{}' -> INVÁLIDO", email),
}
}
}
Saída do Programa
=== Validação com Regex ===
[OK] 'usuario@exemplo.com'
[OK] 'nome.sobrenome@empresa.com.br'
[OK] 'usuario+tag@gmail.com'
[FALHA] 'invalido@'
[FALHA] '@sem-usuario.com'
[FALHA] 'sem-arroba.com'
[FALHA] 'dois@@arrobas.com'
[FALHA] ''
[FALHA] 'a@b.c'
[OK] 'usuario@dominio.com'
[OK] ' espacos@email.com '
=== Validação Manual (com detalhes) ===
[OK] 'usuario@exemplo.com'
[OK] 'nome.sobrenome@empresa.com.br'
[OK] 'usuario+tag@gmail.com'
[FALHA] 'invalido@' -> Domínio está vazio
[FALHA] '@sem-usuario.com' -> Parte do usuário está vazia
[FALHA] 'sem-arroba.com' -> Email não contém @
[FALHA] 'dois@@arrobas.com' -> Email contém mais de um @
[FALHA] '' -> Email vazio
[FALHA] 'a@b.c' -> Domínio não contém ponto
[OK] 'usuario@dominio.com'
[OK] ' espacos@email.com '
=== Validação com crate validator ===
[OK] Cadastro { email: "usuario@exemplo.com", nome: "Ana Silva" }
[FALHA] Cadastro { email: "invalido@", nome: "Bia" } -> email: Email inválido
[FALHA] Cadastro { email: "valido@email.com", nome: "AB" } -> nome: Nome deve ter 3-50 caracteres
=== Processamento de Lista ===
Válidos (3):
ana@empresa.com
bia@gmail.com
carlos@outlook.com
Inválidos (3):
'invalido'
''
'sem-arroba'
=== Normalização ===
' USUARIO@GMAIL.COM ' -> 'usuario@gmail.com'
'Ana@Empresa.COM' -> 'ana@empresa.com'
'invalido' -> INVÁLIDO
Quando Usar Cada Abordagem
| Abordagem | Vantagens | Desvantagens |
|---|---|---|
| Regex | Rápida, padrão conhecido | Regex complexa, mensagens genéricas |
| Manual | Mensagens detalhadas, zero deps | Mais código, pode ter falhas |
validator | Integra com structs, derive macro | Dependência extra |
Recomendação: Para aplicações web, use a crate validator integrada com suas structs de request. Para validações pontuais, regex é suficiente. A validação manual é útil quando você precisa de mensagens de erro detalhadas em português.
Veja Também
- Usar Regex em Rust — guia completo de expressões regulares
- Tratar Erros em Rust — como criar tipos de erro para validação
- Tutorial: API REST com Axum — validação de dados em APIs
- Filtrar Vetor em Rust — filtre listas de emails com partition()
- Rust Cheatsheet — referência rápida de String e operações de texto
- Validação em aplicações IA — padrões de validação em projetos de IA