aprimoração de ia
This commit is contained in:
parent
1177e31e56
commit
4a9f5f70ff
109 changed files with 8835 additions and 55 deletions
238
rules/zod-validacao-padrao.md
Normal file
238
rules/zod-validacao-padrao.md
Normal file
|
|
@ -0,0 +1,238 @@
|
|||
---
|
||||
type: rule
|
||||
name: zod-validacao-padrao
|
||||
description: Estabelece Zod como biblioteca obrigatória de validação em todo o ecossistema Azteca
|
||||
version: "1.0"
|
||||
scope: global
|
||||
priority: alta
|
||||
trigger: always_on
|
||||
globs:
|
||||
- "**/*.ts"
|
||||
- "**/*.tsx"
|
||||
- "**/*.vue"
|
||||
alwaysApply: true
|
||||
updatedAt: "2026-06-01"
|
||||
references:
|
||||
- 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](https://zod.dev).
|
||||
|
||||
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)
|
||||
|
||||
```typescript
|
||||
// ✅ 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
|
||||
|
||||
```typescript
|
||||
// ✅ 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
|
||||
|
||||
```typescript
|
||||
// ❌ 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
|
||||
|
||||
```typescript
|
||||
// ✅ 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
|
||||
|
||||
```typescript
|
||||
// ✅ 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)
|
||||
|
||||
```typescript
|
||||
// ✅ 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
|
||||
|
||||
```typescript
|
||||
// ✅ 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)
|
||||
Loading…
Add table
Add a link
Reference in a new issue