Projeto guiado: client de API com generics e erro tipado
Junta generics, discriminated union e Zod pra um client de API que não confia cegamente em nada que a rede manda.
Na lição 07 você construiu um buscarApi<T> genérico que confiava cegamente no res.json(). Agora, com discriminated union, generics e Zod já vistos nesta trilha, dá pra construir a versão que checa de verdade o que a API devolveu — antes de tratar aquele dado como confiável.
1. O formato do resultado — Ok<T> | Err
Em vez de lançar exceção, a função devolve um discriminado por kind — o mesmo padrão da lição de enums e discriminated unions, só que batizado Ok/Err, nome comum nesse tipo de API:
type Ok<T> = { kind: "ok"; data: T };
type Err = { kind: "err"; error: string };
type Resultado<T> = Ok<T> | Err;2. A função genérica, agora validando com Zod
import { z } from "zod";
async function buscarApi<T>(url: string, schema: z.ZodType<T>): Promise<Resultado<T>> {
try {
const res = await fetch(url);
if (!res.ok) {
return { kind: "err", error: `Erro ${res.status}` };
}
const bruto = await res.json();
const validado = schema.safeParse(bruto);
if (!validado.success) {
return { kind: "err", error: "Formato de resposta inesperado" };
}
return { kind: "ok", data: validado.data };
} catch {
return { kind: "err", error: "Falha de rede" };
}
}O generic <T> garante o tipo certo pra quem chama a função; o schema garante que o valor que chega de verdade bate com aquele tipo. Uma coisa não substitui a outra — T some na compilação, só o schema continua de pé quando o código está rodando.
3. Definindo o schema e derivando o tipo
const PedidoSchema = z.object({
id: z.string(),
status: z.enum(["pendente", "pago", "cancelado"]),
total: z.number(),
});
type Pedido = z.infer<typeof PedidoSchema>;4. Usando — narrowing pelo discriminante kind
async function carregarPedido(id: string) {
const resultado = await buscarApi(`/api/pedidos/${id}`, PedidoSchema);
switch (resultado.kind) {
case "ok":
return resultado.data; // TS sabe: é um Pedido de verdade, checado
case "err":
console.error(resultado.error); // TS sabe: só "error" existe aqui
return null;
}
}Exercício
Escreva uma função buscarUsuario(id: string) que usa buscarApi com um UsuarioSchema ({ nome: string; email: string }) pra buscar /api/usuarios/:id, e trata o Resultado com um switch sobre kind.