fabian-hiller / valibot

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

How to extend a given schema #904

Closed stackoverfloweth closed 2 weeks ago

stackoverfloweth commented 3 weeks ago

using valibot version 0.42.1

How can I conditionally extend a schema that might have v.nullish?

I assumed I could pass the object entries as the first argument to v.pipe and it would retain any optionality it had before.

For example let's say baseSchema is

const baseSchema = v.object({
  foo: v.nullish(v.number())
})

and somewhere else I want to conditionally check that foo is an integer

v.pipe(baseSchema.foo, v.integer())

1.) if baseSchema is strongly typed, I'll get an error on v.integer() because it correctly is telling me that foo could be undefined. What's your suggested approach for adding validation only when the value is not nullish?

2.) if baseSchema is loosely typed, valibot will take a nullish value and throw Invalid integer: Received undefined. I hoped that the baseSchema.foo would retain it's optionality

stackoverfloweth commented 3 weeks ago

looks like my issue was outside of valibot

stackoverfloweth commented 2 weeks ago

my original post was resolved outside of valibot, however I do have a legitimate question about extending schemas. Please take a look at my revised issue explanation

fabian-hiller commented 2 weeks ago

Here is an example that works:

import * as v from 'valibot';

const Schema1 = v.object({
  foo: v.number(),
  bar: v.pipe(v.number(), v.maxValue(300)),
  other: v.string(),
});

const Schema2 = v.object({
  ...Schema1.entries,
  foo: v.pipe(Schema1.entries.foo, v.minValue(250)),
  bar: v.pipe(Schema1.entries.bar, v.minValue(250)),
});
stackoverfloweth commented 2 weeks ago

Here is an example that works:

import * as v from 'valibot';

const Schema1 = v.object({
  foo: v.number(),
  bar: v.pipe(v.number(), v.maxValue(300)),
  other: v.string(),
});

const Schema2 = v.object({
  ...Schema1.entries,
  foo: v.pipe(Schema1.entries.foo, v.minValue(250)),
  bar: v.pipe(Schema1.entries.bar, v.minValue(250)),
});

@fabian-hiller sorry, I just finished editing my question, you were too fast

fabian-hiller commented 2 weeks ago

Here are 3 solutions:

import * as v from 'valibot';

const BaseSchema = v.object({
  foo: v.nullish(v.number()),
});

const OtherSchema1 = v.object({
  foo: v.nullish(v.pipe(v.unwrap(BaseSchema.entries.foo), v.integer())),
});

const OtherSchema2 = v.object({
  foo: v.pipe(
    BaseSchema.entries.foo,
    v.check((input) => input === null || Number.isInteger(input))
  ),
});

const OtherSchema3 = v.pipe(
  BaseSchema,
  v.check((input) => input.foo === null || Number.isInteger(input.foo))
);