vue-componentes/.agent/rules/zod-validacao-padrao.global.md

8.9 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
.agent/rules/typescript-sem-any.global.md
.agent/skills/criar-schema-zod.global/SKILL.md

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 de schemas em vez de duplicação

// ✅ CORRETO — composição
const zEndereco = z.object({
  rua: z.string(),
  cidade: z.string(),
  uf: z.string().length(2),
})

const zEmpresaCriar = z.object({
  nome: z.string(),
  endereco: zEndereco,
})

const zEmpresaEditar = zEmpresaCriar.partial() // todos opcionais

const zEmpresaResposta = zEmpresaCriar.extend({
  codigo: z.string().uuid(),
  criadoEm: z.string().datetime(),
})

// ❌ ERRADO — copiar campos entre schemas
const zEmpresaEditar = z.object({
  nome: z.string().optional(),    // duplicou!
  endereco: z.object({            // duplicou!
    rua: z.string().optional(),
    cidade: z.string().optional(),
  }),
})

9. Enums Zod para valores de domínio

// ✅ CORRETO — enum Zod quando os valores vêm de dados externos
export const zStatusLicenca = z.enum(["ativa", "vencida", "suspensa", "cancelada"])
export type tipoStatusLicenca = z.infer<typeof zStatusLicenca>

// Usar em outros schemas
const zLicencaCriar = z.object({
  status: zStatusLicenca,
  // ...
})

// ✅ TAMBÉM CORRETO — TypeScript enum quando já existe no projeto
// e o schema referencia o enum
import { StatusLicenca } from "./enums"
const zLicencaCriar = z.object({
  status: z.nativeEnum(StatusLicenca),
})

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

Webhook com fila

// ✅ Correto — valida antes de enfileirar
const zPayloadWebhook = z.object({
  tabela: z.enum(["empreendedores", "empreendimentos", "usuarios"]),
  dados: z.array(z.unknown()).min(1),
})

app.post("/webhook/:tabela", async (req, res) => {
  const resultado = zPayloadWebhook.safeParse({
    tabela: req.params.tabela,
    dados: req.body,
  })
  if (!resultado.success) {
    return res.status(400).json({ erro: "Payload inválido" })
  }
  await fila.add("sync", resultado.data)
  return res.json({ ok: true })
})

Variáveis de ambiente

// ✅ Correto — validar no boot da aplicação
const zAmbiente = z.object({
  DATABASE_URL: z.string().url(),
  REDIS_HOST: z.string().default("localhost"),
  REDIS_PORT: z.coerce.number().default(6379),
  BASE_URL: z.string().url(),
  SESSAO_BACK: z.string().uuid(),
  NODE_ENV: z.enum(["development", "production", "test"]).default("development"),
})

export const ambiente = () => zAmbiente.parse(process.env)

Frontend (formulário Vue)

// ✅ Correto — schema compartilhado validando antes de enviar
import { zUsuarioCriar } from "projeto-comuns/tabelas/usuarios"

const salvar = async () => {
  const resultado = zUsuarioCriar.safeParse(formulario.value)
  if (!resultado.success) {
    erros.value = resultado.error.flatten().fieldErrors
    return
  }
  await api.post("/usuarios", resultado.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: .agent/rules/typescript-sem-any.global.md (narrowing com Zod mencionado como alternativa)
  • Skill: .agent/skills/criar-schema-zod.global/SKILL.md (guia prático para criar schemas)