Заставить zod выполнять проверку типа верхнего уровня на входе .parse()

Я понимаю, что цель zod - проанализировать ненадежные входные данные и подтвердить, что они имеют тип, соответствующий вашей схеме.

Но обычно эти данные поступают через веб-API, которые гарантируют, по крайней мере, их форму верхнего уровня, например или.

Похоже, что zod имеет смысл выполнять эту проверку типов верхнего уровня на parse(), хотя бы для предотвращения таких глупых ошибок, как опечатки. Но, похоже, это не так.

В качестве упрощенного примера для иллюстрации

      const schema = z.string().email();
schema.parse(1); // no type error here - why?

Это выглядит как parse(1) должна иметь ошибку типа времени компиляции, потому что мы знаем, что невозможно, чтобы буквальный number1 будет правильно проверяться во время выполнения. Мы не можем сделать это с каким-то случайным stringinput - требуется анализ времени выполнения, чтобы убедиться, что это действительный адрес электронной почты, - но число здесь кажется очевидной ошибкой программиста и не должно даже компилироваться.

Более практический пример, который заставил меня задать этот вопрос

      async function validateRequest(request: Request) {
  const someSchema = z.object({ ... })
  return someSchema.parse(request.json()) // didn't await request.json() so won't work
}

Глупая ошибка, такая как пропуск await выше, кажется, что это должно быть легко поймать someSchema.parse() проверяя, что я прохожу object, а не Promise<object>.

Итак, есть ли способ включить эту проверку типов верхнего уровня с помощью zod?

Или это поведение намеренно по какой-то причине, которую я не понял в дизайне зода?

1 ответ

Я пытался найти способ сделать это в Zod, но не нашел ничего убедительного. Я подозреваю, что вариант использования может не поддерживаться из коробки. Одним из предложений, если вы хотите продолжить синтаксический анализ с помощью Zod, будет создание функции-оболочки с более строгими типами, которая внутренне вызывает вашу схему Zod.

      // I went for a record type because Promise<any> is assignable to object
function parseResponse(input: Record<string, unknown>) {
  const someSchema = z.object({ ... });
  return someSchema.parse(input)
}
async function validateRequest(request: Request) {
  return parseResponse(request.json()) // This will throw because now it sees the promise
}

Редактировать:

На самом деле вы могли бы написать такого рода помощника в целом следующим образом:

      import { z } from "zod";
const schema = z.object({
  field: z.string(),
})

function restrict<T, Output, Def extends z.ZodTypeDef, Input = Output>(schema: z.ZodType<Output, Def, Input>) {
  return (t: T) => schema.parse(t);
}
type InferTypes<Z> = Z extends z.ZodType<infer Output, infer Defs, infer Input> ? [Output, Defs, Input] : [never, never, never];
type InferOutput<Z> = InferTypes<Z>[0];
type InferDefs<Z> = InferTypes<Z>[1];
type InferInput<Z> = InferTypes<Z>[2];

const validate = restrict<
  Record<string, unknown>,
  InferOutput<typeof schema>,
  InferDefs<typeof schema>,
  InferInput<typeof schema>
>(schema);

validate({}); // This typechecks because it's possible
async function foo(req: Request) {
  return validate(req.json()); // This has a type error.
}

Единственным реальным недостатком является стек вывода типа, который вам нужно сделать, чтобы заставить работать дженерики.

Альтернативная библиотека

Если вы открыты для альтернативных библиотек, это то, что относительно первоклассно поддерживается в io-ts. Вы можете явно указать типы ввода и вывода ваших схем, они просто начинаются как по умолчанию. Компромисс заключается в опыте разработчиков. использует функциональную парадигму, на изучение которой уходит много времени.

Другие вопросы по тегам