Z
Zodβ€’12mo ago
addamsson

addamsson - I have the following code:tsimpor...

I have the following code:
import { z } from "zod";

const Address = z.object({
street: z.string().min(1),
city: z.string().min(1),
zip: z.number().gt(0),
country: z.string().min(1),
});

const User = z
.object({
name: z.string().min(1),
age: z.number().min(0),
email: z.string().email(),
from: z.number(),
to: z.number(),
addresses: z.array(Address),
})
.refine(({from, to}) => from < to, {
message: "From and to is not OK.",
path: ["interval"],
});

describe("Given a ValidationError", () => {
test("Then", () => {
const result = User.safeParse({
name: "",
age: -1,
email: "not an email",
from: 1,
to: 0,
addresses: [{
street: "somewhere 1",
city: "nowhere",
zip: 6667,
}],
});

if (result.success) {
throw new Error("Should have failed.");
} else {
console.log(result.error.errors);
}
});
});
import { z } from "zod";

const Address = z.object({
street: z.string().min(1),
city: z.string().min(1),
zip: z.number().gt(0),
country: z.string().min(1),
});

const User = z
.object({
name: z.string().min(1),
age: z.number().min(0),
email: z.string().email(),
from: z.number(),
to: z.number(),
addresses: z.array(Address),
})
.refine(({from, to}) => from < to, {
message: "From and to is not OK.",
path: ["interval"],
});

describe("Given a ValidationError", () => {
test("Then", () => {
const result = User.safeParse({
name: "",
age: -1,
email: "not an email",
from: 1,
to: 0,
addresses: [{
street: "somewhere 1",
city: "nowhere",
zip: 6667,
}],
});

if (result.success) {
throw new Error("Should have failed.");
} else {
console.log(result.error.errors);
}
});
});
My problem is no matter what I do or where I place that refine I never get the from/to error. What am I doing wrong?
37 Replies
Svish
Svishβ€’12mo ago
Why do you have path: ['interval']? I don't see any field called interval there. path is supposed to be "the path this error should be attached to" You should pick either from or to, and change the path and the message to make sense of it. If your path is to, then the message should be related to to, like "To cannot be less than From", or whatever
addamsson
addamssonOPβ€’12mo ago
It fails even if I remove that, I just tried some things out hoping that something will work I also tried zod-validation-error but it does literally nothing lol i mean it ignores from/to as well
Svish
Svishβ€’12mo ago
You can't remove it, you have to change the path to target an actual field
addamsson
addamssonOPβ€’12mo ago
i mean I'm looking at the docs:
const myString = z.string().refine((val) => val.length <= 255, {
message: "String can't be more than 255 characters",
});
const myString = z.string().refine((val) => val.length <= 255, {
message: "String can't be more than 255 characters",
});
and it doesn't say anything about path it says I can use it but nothing happens regardless I dont' see this error in the output
Svish
Svishβ€’12mo ago
ah, no, because there it's just on a string. In your case you refine an object, so you should provide a path to the field the message should be attached to. Ah, sorry, you mean the validation doesn't even fail at all?
addamsson
addamssonOPβ€’12mo ago
i haven't checked that, i'm just looking at the resulting error, and it doesn't contain this message
Svish
Svishβ€’12mo ago
what does it contain?
addamsson
addamssonOPβ€’12mo ago
lemme paste it this is the result of flatten()
{
formErrors: [],
fieldErrors: {
name: [ 'String must contain at least 1 character(s)' ],
age: [ 'Number must be greater than or equal to 0' ],
email: [ 'Invalid email' ],
addresses: [ 'Required' ]
}
}
{
formErrors: [],
fieldErrors: {
name: [ 'String must contain at least 1 character(s)' ],
age: [ 'Number must be greater than or equal to 0' ],
email: [ 'Invalid email' ],
addresses: [ 'Required' ]
}
}
but if I introspect the object I don't see it still so it is not an issue with flatten()
Svish
Svishβ€’12mo ago
Right, so the nice/annoying thing about zod is that the validation happens in "stages", so when you have an object, with a refine, the object must be completely valid before the refine even happens
addamsson
addamssonOPβ€’12mo ago
damn that's bad
Svish
Svishβ€’12mo ago
So if you make all the fields in the object valid first, then you should see the refine error I.e. all fields are valid, and to and from are numbers, then refine should kick in and check if to and from are OK in relation to each other
addamsson
addamssonOPβ€’12mo ago
the problem is that if the user supplies a negative age, and a bad from/to then i can only display the age problem, and when they fix it they'll bump into from/to which is bad ux
Svish
Svishβ€’12mo ago
Yeah. Zod schemas is great for consistency and predictability, and awesome for parsing and validating data. But at least I found that the moment you need to report errors to users in the context of forms, and you have fields that depend on each other, then zod becomes cumbersome and annoying pretty quickly.
addamsson
addamssonOPβ€’12mo ago
bummer
Svish
Svishβ€’12mo ago
We have some form and wizard stuff built on top of react-hook-form and started with zod for validation, and it's great for small simple forms, but yeah, for more complicated stuff with interdependent fields, the refine stuff became super annoying.
addamsson
addamssonOPβ€’12mo ago
i have like 100 zod schemas in my project lol πŸ˜„
Svish
Svishβ€’12mo ago
So we ended up trying out yup instead for that, and it's a bit more janky, and I definitely don't trust its types as much as I do zod, but it has a .when feature which is pretty great for these kind of "if this field is that, then this field should validate this way, except if, blah blah"-cases We're still using zod to validate API responses, JSON configs and data we store in localStorage though, so we actually have both zod and yup in the same project
addamsson
addamssonOPβ€’12mo ago
hm
Svish
Svishβ€’12mo ago
It's easy to think that we need to use a single thing for every thing, but why not just pull in a couple of alternatives and use them for what they're best at πŸ€·β€β™‚οΈ
addamsson
addamssonOPβ€’12mo ago
i used io-ts before migrated to zod before the simplicity
addamsson
addamssonOPβ€’12mo ago
then someone suggested this: https://github.com/Effect-TS/schema
GitHub
GitHub - Effect-TS/schema: Modeling the schema of data structures a...
Modeling the schema of data structures as first-class values - GitHub - Effect-TS/schema: Modeling the schema of data structures as first-class values
addamsson
addamssonOPβ€’12mo ago
have you used this before?
Svish
Svishβ€’12mo ago
We use yup for complicated forms, and zod for everything else. Has worked pretty well so far πŸ™‚
Svish
Svishβ€’12mo ago
Think I'll stick with zod and yup for now πŸ˜› https://npmtrends.com/@effect/schema-vs-yup-vs-zod
No description
addamsson
addamssonOPβ€’12mo ago
well if you input fp-ts or effect into that you'll get similar numbers probably yet i found both superior to literally everything else so these numbers aren't really useful but maybe it makes sense if i use zod on the backend and something else on the frontend for complex stuff
Svish
Svishβ€’12mo ago
For professional projects, I much rather use something not dead with high fairly long term usage, than a tiny project that is barely used by anyone, and very likely might be abandoned in a few months. Doesn't matter if the project is superior, if nobody else uses it, it's just maintained by a single person, and it's likely to die. πŸ˜› Anyways, I'd recommend not throwing away zod just because it's not optimal for these complex things. I'd just keep using zod for what it's great at, and find an alternative for the complex stuff. yup as worked pretty well for us, but there are probably other good alternatives as well
addamsson
addamssonOPβ€’12mo ago
just because not many folks are using it doesn't mean that it is dead / dying. I have been using fp-ts for a very long time and the contributors are very active. as long as you're using someone else's code there is always a chance that it will be abandoned at some point. not even big projects are free of this issue (just think about all the projects google killed).
Svish
Svishβ€’12mo ago
Yeah, of course not. I'm not saying usage numbers is the only metric, just that it's a good one to have part of the decision making. Others are how long the project have been going, how active the maintainers are, number and age of open PRs and issues that, etc., etc. Anyways, back to work for me 😊
addamsson
addamssonOPβ€’12mo ago
πŸ‘
Scott Trinh
Scott Trinhβ€’12mo ago
I can weigh in here a bit: effect and effect/schema are great tools, and I would recommend them just based on their pedigree, but I don't have any practical experience with them. I would imagine they would suffer from this same "stages" issue though, due to how types need to propagate from the individual properties of an object then to the object itself: what would the type of refine's input be if we didn't know the types of the properties? Others have suggested having some function that lets you use unknown here, but lemme tell you: it'll be a nightmare in practice to handle unknown in a safe and consistent wayβ€”this is why Zod exists in the first place πŸ˜… Yup makes the tradeoff of safety for flexibility and I think that's usually the right call for very complex forms: better UX and using a Zod schema server-side to re-parse the values to ensure safety. Some code duplication and syncing effort involved, for sure, but I agree with @Svish that it's really the right call a lot of the time. FWIW, I think this is something that form frameworks can solve by introspecting the schema and cleverly constructing "valid" objects with fallbacks and collecting the refinement errors, etc, but none have yet added this level of integration.
Svish
Svishβ€’12mo ago
unknown is indeed a pain to deal with, I'm just thinking that some sort of .extraValidation(value: unknown, ctx; ZodContextThatYouCanAddIssuesTo): void function could add that little bit extra flexibility and allow people who do want to deal with the pain, rather than pulling in a whole other validation library, to keep using zod. Maybe it could have a very minor validation before running to narrow down the input type somewhat still. Like, if you use it on object, it would check that the input value is an object, but give no guarantee whatsoever at what the object contains. Then you could do something along the lines of if('to' in value && 'from' in value && to < from) ctx.addIssue(...) Even if left as unknown, it's not that terrible to write a simple type-guard ourselves for whatever we need. Could technically even use zod within zod πŸ˜›
Scott Trinh
Scott Trinhβ€’12mo ago
Yeah, the zod-in-zod approach is how I used preprocess and it has it's basically reintroduces the "stages" but gives you some flexibility to use safeParse.
Svish
Svishβ€’12mo ago
Yeah, but some sort of .extraValidation that you could attach to any schema that was called regardless and just let you add issues to the context, without affecting the data or types at all, I think could solve some of these issues people have with form validation. Of course it won't be super optimal or give great DX, but it could give you a way to solve some of these simpler cases.
Scott Trinh
Scott Trinhβ€’12mo ago
Maybe something like finally(value, ctx)? where value: unknown
Svish
Svishβ€’12mo ago
Yeah, something like that, is what I'm thinking at least If at all possible, maybe the bare minimum of validation on the value. Like if it's on ZodString, validate that it's a string at least. And if it's on ZodObject, validate that it's the bare minimum of an object. But yeah, main focus that it should not mess with any typings or existing functionality in any way
Scott Trinh
Scott Trinhβ€’12mo ago
I think that would be introduce subtle cases where you still don't get the finally firing (because your provided a number or null for instance) and therefore end up back in this same thing where some errors aren't present until data is in a certain shape, which does move the issue "back" a bit and lets more cases through, but doesn't seem worth introducing if it doesn't give you the tools to get the right UX you're after still.
Svish
Svishβ€’12mo ago
Yeah, good point. Best keep it simple and unknown πŸ‘
Want results from more Discord servers?
Add your server