fabian-hiller / valibot

The modular and type safe schema library for validating structural data 🤖
https://valibot.dev
MIT License
5.88k stars 181 forks source link

Unable to use partial() with SchemaWithPipe #701

Closed rmuchall closed 2 months ago

rmuchall commented 2 months ago

I'm attempting to use v.partial() with a schema that uses a pipe. However, I'm receiving the following error: "TS2345: Argument of type SchemaWithPipe is not assignable to parameter of type Schema$4" EDIT: I am using valibot v0.36 If I ignore this type error and execute the code then partial does not seem to work. Validation errors (issues) are generated for each of the properties in the schema: e.g. "User name is required", "Password is required" etc

import * as v from "valibot";
import {expect, test} from "vitest";

test("email validation", () => {
  const TestSchema = v.pipe(
    v.object({
      userName: v.pipe(
        v.string("User name is required"),
        v.minLength(2, "User name must be at least 2 characters"),
        v.regex(
          /^[a-z0-9]+$/i,
          "User name must only contain alphanumeric characters",
        ),
      ),
      password: v.pipe(
        v.string("Password is required"),
        v.minLength(8, "Password must be at least 8 characters"),
      ),
      confirmPassword: v.pipe(
        v.string("Confirm password is required"),
        v.minLength(8, "Confirm password must be at least 8 characters"),
      ),
      email: v.pipe(
        v.string("Email is required"),
        v.email("Email must be a valid email address"),
      ),
    }),
    v.forward(
      v.check(
        (input) => input.password === input.confirmPassword,
        "Both passwords must be the same"), ["confirmPassword"],
    ),
  );

  // Error occurs on this line:
  const safeParseResult = v.safeParse(v.partial(TestSchema), {email: "xxx"});

  expect(safeParseResult.success).toBe(false);
});
fabian-hiller commented 2 months ago

Combining partial with pipe can lead to runtime errors because the types, for example of your check action, are different. Therefore, we do not allow you to use partial with a schema that has a pipeline. The workaround is to pass the schema without a pipeline and add the pipeline afterwards.

import * as v from 'valibot';

type PasswordInput = {
  password?: string;
  confirmPassword?: string;
};

function checkPassword<TInput extends PasswordInput>() {
  return v.forward<TInput, v.PartialCheckIssue<TInput>>(
    v.partialCheck(
      [['password'], ['confirmPassword']] as v.PathKeys<TInput>[],
      (input) => input.password === input.confirmPassword,
      'Both passwords must be the same'
    ),
    ['confirmPassword'] as v.PathKeys<TInput>
  );
}

const BaseSchema = v.object({
  userName: v.pipe(
    v.string('User name is required'),
    v.minLength(2, 'User name must be at least 2 characters'),
    v.regex(
      /^[a-z0-9]+$/iu,
      'User name must only contain alphanumeric characters'
    )
  ),
  password: v.pipe(
    v.string('Password is required'),
    v.minLength(8, 'Password must be at least 8 characters')
  ),
  confirmPassword: v.pipe(
    v.string('Confirm password is required'),
    v.minLength(8, 'Confirm password must be at least 8 characters')
  ),
  email: v.pipe(
    v.string('Email is required'),
    v.email('Email must be a valid email address')
  ),
});

const BaseSchemaWithPipe = v.pipe(BaseSchema, checkPassword());

const PartialBaseSchemaWithPipe = v.pipe(
  v.partial(BaseSchema),
  checkPassword()
);
rmuchall commented 2 months ago

Many thanks for your fast response and a great library :)