Lendo erros de validação
Um parse que falha devolve um erro. Se você só der um console.log nele, parece uma parede de texto sem saída.
Mas não é. Tudo que você precisa para colocar uma mensagem no campo certo do formulário está ali, num formato pensado para ser lido por código.
Comece com um schema que dois valores conseguem quebrar:
import * as z from 'zod'
const teacherSchema = z.object({
name: z.string(),
age: z.number().min(18),
})
const result = teacherSchema.safeParse({ name: 12345, age: 13 })O array de issues
O conteúdo útil do erro fica em .issues, e é um array porque um único parse pode encontrar vários problemas de uma vez:
console.log(result.error.issues.length)
// 2Dois valores estavam errados, então há duas issues. Cada uma é um objeto descrevendo um único problema:
console.log(result.error.issues[0])
// {
// expected: 'string',
// code: 'invalid_type',
// path: [ 'name' ],
// message: 'Invalid input: expected string, received number'
// }Quatro campos que vale a pena conhecer:
| Campo | O que ele indica |
|---|---|
code | O tipo de falha, como invalid_type ou too_small |
path | Qual chave falhou, como um array |
message | Uma frase descrevendo o problema |
expected | O que o schema esperava, em falhas de tipo |
A comparação com o scanner continua valendo aqui. A mensagem do console é o resumo que a máquina imprime no visor. issues é o relatório detalhado por trás dela, e é com esse que você trabalha.
O array está lá o tempo todo. Use .issues e você recebe a versão estruturada, uma entrada para cada coisa que deu errado.
Qual campo falhou
path é um array em vez de uma string, porque uma chave pode estar aninhada:
const orderSchema = z.object({
user: z.object({
profile: z.object({
email: z.email(),
}),
}),
})
const bad = orderSchema.safeParse({ user: { profile: { email: 'nope' } } })
console.log(bad.error.issues[0].path)
// [ 'user', 'profile', 'email' ]Para um formulário simples, o primeiro elemento é o nome do campo, o que já basta para construir o que um formulário precisa:
const errors = result.error.issues.map((issue) => ({
field: issue.path[0],
message: issue.message,
}))
console.log(errors)
// [
// { field: 'name', message: 'Invalid input: expected string, received number' },
// { field: 'age', message: 'Too small: expected number to be >=18' }
// ]Um nome de campo e uma frase, um para cada problema. É esse o formato de que um formulário precisa para colocar cada mensagem ao lado do campo a que ela pertence.
['user', 'profile', 'email'] é um conjunto de instruções: entre em user, depois em profile, depois em email. Uma string simples não conseguiria dizer isso sem você ter que desmontá-la de novo.
Escrevendo suas próprias mensagens
As mensagens padrão descrevem o sistema de tipos, não o seu formulário. "Invalid input: expected string, received number" está correta e não pertence ao usuário de ninguém.
Quase todo método do Zod aceita uma mensagem como argumento:
const teacherSchema = z.object({
name: z.string('Please enter your name'),
age: z.number().min(18, 'Teachers must be at least eighteen'),
})
const result = teacherSchema.safeParse({ name: 12345, age: 13 })
console.log(result.error.issues.map((issue) => issue.message))
// [ 'Please enter your name', 'Teachers must be at least eighteen' ]A mensagem substitui o padrão apenas para aquela checagem específica. Cada restrição carrega a sua própria, então um campo pode dizer uma coisa quando está vazio e outra quando é muito curto.
Quem lê não está depurando o seu schema. Está tentando se cadastrar.
Coloque em prática
Dado este schema e um personagem que quebra as duas regras dele:
const characterSchema = z.object({
name: z.string('Every character needs a name'),
episode: z.number().min(1, 'Episodes start at 1'),
})
const character = { name: 42, episode: 0 }Seis lacunas, marcadas com ___. Cada uma de success, result, error, issues, path e message se encaixa em exatamente uma delas:
const ___ = characterSchema.safeParse(character)
if (result.___) {
console.log('All good')
} else {
console.log(
result.___.___.map((issue) => ({
field: issue.___[0],
message: issue.___,
})),
)
}Compare suas respostas
const result = characterSchema.safeParse(character)
if (result.success) {
console.log('All good')
} else {
console.log(
result.error.issues.map((issue) => ({
field: issue.path[0],
message: issue.message,
})),
)
}Resolvendo em ordem:
- A primeira lacuna nomeia o que
safeParseretorna, e tudo abaixo lêresult, então tem que serresult. result.successé o booleano que decide qual ramo roda.result.errorsó existe no ramo de falha, e é por isso que fica dentro doelse..issuesé o array, então o.mapprecisa dele.issue.path[0]é o nome do campo, indexado porquepathé um array.issue.messageé a frase.
A cadeia se lê como uma única frase quando faz sentido: as issues do error do result, cada uma com um path e uma message.
As duas regras quebram, então são logadas duas entradas:
// [
// { field: 'name', message: 'Every character needs a name' },
// { field: 'episode', message: 'Episodes start at 1' }
// ]Para onde isso leva
Três capítulos de Zod, tudo rodado manualmente com objetos inventados. O schema está fazendo um trabalho de verdade, mas nada ainda está conectado à aplicação.
Onde a validação se encaixa conecta isso à aplicação, colocando a checagem entre uma requisição que chega e o código que age sobre ela.

