.agent/rules/zod-validacao-padrao.md
2026-08-10 16:18:49 -03:00

7.2 KiB

type name description version scope priority trigger globs alwaysApply updatedAt references
rule zod-validacao-padrao Estabelece Zod como biblioteca obrigatória de validação em todo o ecossistema Azteca 1.0 global alta always_on
**/*.ts
**/*.tsx
**/*.vue
true 2026-06-01
global/rules/typescript-sem-any
global/skills/criar-schema-zod/SKILL

Regra: Zod como Validador Padrão

Contexto

Todo projeto do ecossistema Azteca que receba dados externos — API, formulário, webhook, importação, fila, variáveis de ambiente — deve validar esses dados com Zod.

Zod é a única biblioteca de validação autorizada porque:

  • Inferência nativa de tipos TypeScript (z.infer<>) — elimina duplicação entre schema e tipo
  • Bundle pequeno (~13KB gzipped) — viável no frontend
  • Compartilhável entre back e front via pacotes *-comuns
  • Ecossistema maduro: zod-to-json-schema, @anatine/zod-nestjs, integração com tRPC

Regra

1. Toda entrada externa deve ser validada com Zod

Entradas externas incluem:

  • Corpo de requisição (API REST, webhook, GraphQL)
  • Query params e path params
  • Formulários de frontend
  • Dados de fila (BullMQ, Redis, etc.)
  • Arquivos importados (CSV, JSON, Excel)
  • Variáveis de ambiente
  • Respostas de APIs externas (quando a resposta não é confiável)

2. Schemas devem residir no pacote *-comuns

Para que front e back compartilhem os mesmos schemas, eles devem residir no pacote <modulo>-comuns:

<modulo>-comuns/src/
├── tabelas/
│   ├── usuarios/
│   │   ├── zUsuarioCriar.ts
│   │   ├── zUsuarioEditar.ts
│   │   └── index.ts
│   ├── empresas/
│   │   ├── zEmpresaCriar.ts
│   │   └── index.ts
│   └── index.ts

3. Tipos devem ser derivados dos schemas (nunca o inverso)

// ✅ CORRETO — tipo derivado do schema
export const zUsuarioCriar = z.object({
  nome: z.string().min(1),
  email: z.string().email(),
  ativo: z.boolean().default(true),
})
export type tipoUsuarioCriar = z.infer<typeof zUsuarioCriar>

// ❌ ERRADO — tipo manual separado do schema
interface UsuarioCriar {
  nome: string
  email: string
  ativo?: boolean
}
const zUsuarioCriar = z.object({ ... }) // duplicação!

4. Nomenclatura obrigatória de schemas

Padrão Uso Exemplo
z{Entidade}Criar Criação de registro zUsuarioCriar
z{Entidade}Editar Edição de registro zUsuarioEditar
z{Entidade}Filtro Filtros de listagem zUsuarioFiltro
z{Entidade}Resposta Resposta de API zUsuarioResposta
z{Entidade}Params Parâmetros de rota zUsuarioParams
z{Modulo}Config Configuração do módulo zGeeConfig
z{Modulo}Ambiente Variáveis de ambiente zBackAmbiente

Prefixo z minúsculo + nome em PascalCase.

5. Usar safeParse em vez de parse para erros controlados

// ✅ CORRETO — erro controlado
const resultado = zUsuarioCriar.safeParse(dados)
if (!resultado.success) {
  return respostaComuns.erro(resultado.error.issues[0].message)
}
const usuario = resultado.data // tipado automaticamente

// ❌ ERRADO — throw não controlado
const usuario = zUsuarioCriar.parse(dados) // lança ZodError

A exceção é em contextos onde o throw é desejável (middleware global, início de pipeline).

6. Nunca usar validação manual quando Zod resolve

// ❌ ERRADO — validação manual
if (!dados.nome || typeof dados.nome !== "string") {
  return erro("Nome é obrigatório")
}
if (!dados.email || !dados.email.includes("@")) {
  return erro("Email inválido")
}

// ✅ CORRETO — Zod faz tudo
const resultado = zUsuarioCriar.safeParse(dados)
if (!resultado.success) {
  return erro(resultado.error.issues[0].message)
}

7. Transformações e defaults pertencem ao schema

// ✅ CORRETO — transformação no schema
export const zProdutoCriar = z.object({
  nome: z.string().trim().min(1),
  preco: z.coerce.number().positive(),
  ativo: z.boolean().default(true),
  codigo: z.string().toUpperCase().optional(),
  tags: z.array(z.string()).default([]),
})

// ❌ ERRADO — transformação manual após validação
const produto = zProdutoCriar.parse(dados)
produto.nome = produto.nome.trim()
produto.codigo = produto.codigo?.toUpperCase()

8. Composição e Enums Zod

// ✅ Composição de schemas via extend/partial
const zEndereco = z.object({ rua: z.string(), cidade: z.string() })
const zEmpresaCriar = z.object({ nome: z.string(), endereco: zEndereco })
const zEmpresaEditar = zEmpresaCriar.partial()

// ✅ Enum Zod para valores de domínio
export const zStatusLicenca = z.enum(["ativa", "vencida", "suspensa", "cancelada"])
export type tipoStatusLicenca = z.infer<typeof zStatusLicenca>

10. Bibliotecas proibidas para validação

❌ Proibido Motivo
joi Não infere tipos TS, bundle pesado
yup Inferência fraca, API menos ergonômica
class-validator Decorators, não compartilhável com frontend
io-ts Verboso, curva de aprendizado alta
ajv (direto) JSON Schema — usar zod-to-json-schema se precisar de JSON Schema
Validação manual com if/typeof Quando Zod resolve o mesmo caso

Exemplos

API backend (Express)

// ✅ Correto
import { zUsuarioCriar } from "projeto-comuns/tabelas/usuarios"

app.post("/usuarios", async (req, res) => {
  const resultado = zUsuarioCriar.safeParse(req.body)
  if (!resultado.success) {
    return res.status(400).json({ erro: resultado.error.issues })
  }
  const usuario = await criarUsuario(resultado.data)
  return res.json(usuario)
})

Variáveis de ambiente e Frontend

// ✅ Validar env no boot da aplicação
export const ambiente = () => z.object({
  DATABASE_URL: z.string().url(),
  NODE_ENV: z.enum(["development", "production", "test"]).default("development"),
}).parse(process.env)

// ✅ Frontend: usar schema compartilhado antes de enviar
const salvar = async () => {
  const res = zUsuarioCriar.safeParse(formulario.value)
  if (!res.success) return (erros.value = res.error.flatten().fieldErrors)
  await api.post("/usuarios", res.data)
}

Impacto

Sem esta regra:

  • Cada desenvolvedor escolhe uma estratégia diferente de validação (if/typeof, joi, yup, etc.)
  • Tipos e schemas ficam dessincronizados — mudança no schema não reflete no tipo e vice-versa
  • Validações manuais são incompletas e inconsistentes entre endpoints
  • Código de validação é duplicado entre front e back
  • Dados inválidos passam despercebidos e corrompem a base

Com esta regra:

  • Uma única fonte de verdade: o schema Zod
  • Tipos TypeScript sempre sincronizados via z.infer<>
  • Validação consistente em todos os pontos de entrada
  • Reutilização entre front, back e workers via pacote *-comuns
  • Erros de validação estruturados e internacionalizáveis

Referências

  • Rule: global/rules/typescript-sem-any (narrowing com Zod mencionado como alternativa)
  • Skill: global/skills/criar-schema-zod/SKILL (guia prático para criar schemas)