zod icon indicating copy to clipboard operation
zod copied to clipboard

z.string().url() seems to accept any string

Open obrassard opened this issue 2 years ago • 27 comments

Hello, I think I may have found a bug with URL validation.

Indeed, I am trying to validate an object which has a property “ticketLink" that should be an URL (z.string().url())

This is my schema :


const schema = z.object({
    projectId: z.string().uuid(),
    title: z.string().min(5).max(100),
    description: z.string().max(255).optional(),
    punchTypeId: z.string().uuid(),
    hours: z.number().min(0.25).max(24),
    ticketLink: z.string().url(),
    billable: z.boolean().optional(),
    date: z.date(),
});

However, no matter what I am setting as a value for the ticketLink property, the schema does not return an error for this property, even if the value is not a valid URL.

For instance, validating this object :

const formState = {"date": 2023-03-23T20:00:05.466Z, "projectId": "28dfcf92-5b36-4976-1d48-08db1f3ca426", "ticketLink": "abc"}
schema.safeParse(formState)

doesn't raise an error for the ticketLink field, but "abc" should not be accepted as an URL.

We're using "zod": "3.21.4"

obrassard avatar Mar 23 '23 20:03 obrassard

This is working fine for me, I'm getting the Invalid url message in the errors list.

import { z } from 'zod';

const schema = z.object({
  projectId: z.string().uuid(),
  title: z.string().min(5).max(100),
  description: z.string().max(255).optional(),
  punchTypeId: z.string().uuid(),
  hours: z.number().min(0.25).max(24),
  ticketLink: z.string().url(),
  billable: z.boolean().optional(),
  date: z.date(),
});

const formState = {
  date: '2023-03-23T20:00:05.466Z',
  projectId: '28dfcf92-5b36-4976-1d48-08db1f3ca426',
  ticketLink: 'abc',
};

const result = schema.safeParse(formState);
if (!result.success) console.log(result.error.issues);

ahmafi avatar Mar 25 '23 06:03 ahmafi

Everything is working as expected for me. Please send a reproducible example if you are still having issues.

const schema = z.object( {
    ticketLink: z.string().url(),
} )

const formState = {
    ticketLink: 'abc',
}
const result = schema.safeParse( formState )
!result.success && console.log( result.error.issues )
// [
//     {
//         validation: 'url',
//         code: 'invalid_string',
//         message: 'Invalid url',
//         path: [ 'ticketLink' ]
//     }
// ]

JacobWeisenburger avatar Mar 27 '23 01:03 JacobWeisenburger

Thank you both for your response !

It is not impossible that there is a mistake in my code or something else I haven't setup properly...

I will retry and keep you updated.

obrassard avatar Mar 27 '23 01:03 obrassard

I re-verified my code and can't understand why it still seems to skip the validation of this field.

Here's the real code snippet used in my app where I added some console.log for debugging. This function is a recoil selector in a react native app.

export const punchCreationFormValidationSelector = selector({
    key: "punchCreationFormValidationSelector",
    get: ({ get }) => {
        const formState = get(punchCreationFormStateAtom);
    
        const schema = z.object({
            projectId: z.string({required_error: "Ce champ est requis"}).uuid("Identifiant du projet invalide"),
            title: z.string({required_error: "Ce champ est requis"}).trim().min(5, "Le titre du punch doit avoir au moins 5 caractères").max(100, "Le titre du punch doit être inférieur à 100 caractères"),
            description: z.string().max(255, "La description du punch doit être inférieure à 255 caractères").optional(),
            punchTypeId: z.string({required_error: "Ce champ est requis"}).uuid("Identifiant du type de punch invalide"),
            hours: z.coerce.number({required_error: "Ce champ est requis", invalid_type_error: "Ce champ est requis"}).min(0.25, "Le nombre d'heures doit être supérieur à 0.25").max(24, "Le nombre d'heures doit être inférieur à 24"),
            ticketLink: z.string().url(),
            billable: z.boolean(),
            date: z.date({required_error: "Ce champ est requis"}),
        })

        console.log("Form state for validation", formState)
        const result = schema.safeParse(formState);
        console.log("result.success", result.success);

        if (!result.success) {
            const errors = result.error.flatten();
            console.log(errors);
            return {
                success: false,
                fieldErrors: errors.fieldErrors,
                data: undefined,
            }
        } else {
            return {
                success: true,
                fieldErrors: undefined,
                data: result.data,
            }
        }
    },
});

As we can see in the console output, the ticketLink field is defined with an invalid value in the formState object. Neverteless, result.success is true and there is no error for this field.

Capture d’écran, le 2023-03-28 à 16 54 19

However, what's weird is that I tested essentially the same code in a standalone js file with the same version of Zod (outside the React app) and it works properly.

zodtest.mjs

import { z } from "zod";

const schema = z.object({
    projectId: z.string({required_error: "Ce champ est requis"}).uuid("Identifiant du projet invalide"),
    title: z.string({required_error: "Ce champ est requis"}).trim().min(5, "Le titre du punch doit avoir au moins 5 caractères").max(100, "Le titre du punch doit être inférieur à 100 caractères"),
    description: z.string().max(255, "La description du punch doit être inférieure à 255 caractères").optional(),
    punchTypeId: z.string({required_error: "Ce champ est requis"}).uuid("Identifiant du type de punch invalide"),
    hours: z.coerce.number({required_error: "Ce champ est requis", invalid_type_error: "Ce champ est requis"}).min(0.25, "Le nombre d'heures doit être supérieur à 0.25").max(24, "Le nombre d'heures doit être inférieur à 24"),
    ticketLink: z.string().url(),
    billable: z.boolean(),
    date: z.date({required_error: "Ce champ est requis"}),
});

const data = {
    "billable": true,
    "date": new Date("2023-03-20"),
    "hours": "1",
    "projectId": "28dfcf92-5b36-4976-1d48-08db1f3ca426",
    "punchTypeId": "e1a28cff-b39e-4d5f-f0d6-08db2c16a4af",
    "ticketLink": "notanurl",
    "title": "Punch title"
}

console.log(data);
const result = schema.safeParse(data);
console.log(result.success);
console.log(result.error.flatten().fieldErrors);
Capture d’écran, le 2023-03-28 à 17 01 25

So, I don't know if the issue is related to Zod or if it relates to recoil, but it's strange since all other fields are validated properly in both scenarios.

obrassard avatar Mar 28 '23 21:03 obrassard

Another interesting thing I found is that if I add other validation rules to ticketLink e.g. ticketLink: z.string().min(2).url() the min(2) rule is enforced properly.

For instance, ticketLink: 'ab' will not pass, but ticketLink: 'abc' does... which means that the formState.ticketLink field is indeed validated by zod, but the url() rule is not enforced.

Edit : using a regex() to verify the URL format also worked as expected, which lead me to think the real issue may be that url() do not work properly with React Native

obrassard avatar Mar 28 '23 21:03 obrassard

@obrassard this is definitely a React Native new URL bug:

https://github.com/colinhacks/zod/issues/2256#issuecomment-1493780042

I couldn't find an upstream tracker issue - maybe that's something you could help us hunt down?:

https://github.com/facebook/react-native/issues/

crutchcorn avatar Apr 03 '23 06:04 crutchcorn

Is there any update on this matter ? Or could you provide a specific way to handle this case on native with zod ? Thanks in advance.

anckaertv avatar May 22 '23 18:05 anckaertv

Still facing this issue.

delivey avatar Jun 15 '23 17:06 delivey

+1 can confirm it does not work in react native

1finedev avatar Aug 23 '23 16:08 1finedev

+1 can confirm it does not work in react native also

Tonysw2 avatar Sep 12 '23 20:09 Tonysw2

I have the same problem in react native

axinzi avatar Sep 27 '23 06:09 axinzi

I have the same issue in Vue 3

krisztina-tropee avatar Nov 07 '23 18:11 krisztina-tropee

Are any corrections expected, or is it better to use some custom validation?

aaalll avatar Nov 10 '23 10:11 aaalll

Using: "zod": "^3.22.4" I can reproduce a similar issue in SvelteKit where the only thing required to return valid is that the string begins with a letter followed by a colon x:.

Returns Valid: http:example.com http:.......///broken.com http: a:example.com b: C: WWW:WWW.COM e:911 call:411

Returns Invalid: anything that does not start with a letter followed by a colon

Have reverted to using regex for urls for now.

ThingEngineer avatar Dec 13 '23 05:12 ThingEngineer

Additional hostname validation solves most of edge cases but it prevents from using localhost and custom aliases.

RobertPechaCZ avatar Dec 13 '23 11:12 RobertPechaCZ

Till then

const schema = z.object({
  meetingLink: z
    .string()
    .refine((value) => /^(https?):\/\/(?=.*\.[a-z]{2,})[^\s$.?#].[^\s]*$/i.test(value), {
      message: 'Please enter a valid URL',
    }),
});

badalsaibo avatar Jan 31 '24 06:01 badalsaibo

I am seeing this issue in Vue3, as well.

This URL is valid: http://localenv.website.com/events/create

This URL (note the leading D) is not valid, but Zod passes validation: Dhttp://localenv.website.com/events/create

Version:

"node_modules/zod": {
            "version": "3.22.4",
            "resolved": "https://registry.npmjs.org/zod/-/zod-3.22.4.tgz",

I am using this through @vee-validate/zod and this is the validation rule:

z.optional(z.string().url('Must be a valid URL'))

ekeyte avatar Mar 18 '24 23:03 ekeyte

Till then

z.object({
  meetingLink: z
    .string()
    .refine((value) => /^(https?):\/\//i.test(value), {
      message: 'Please enter a valid URL',
    }),
});

Almost perfect... when I input "https://", it accepts :(

c0h1b4 avatar Mar 29 '24 11:03 c0h1b4

This worked for me: const urlRegex = /^(?:\w+:)?//([^\s.]+.\S{2}|localhost[:?\d])\S$/; const schema = z.object({ website: z.string().refine((value) => urlRegex.test(value), { message: 'Invalid URL format', }), });

jarriola-designli avatar May 09 '24 21:05 jarriola-designli

Hiya, also having the same issue. :(

vexkiddy avatar May 30 '24 19:05 vexkiddy

I'm having the same issue with react 18.2.0 (no react native) & zod 3.22.2

daviddwmd avatar Jun 17 '24 17:06 daviddwmd

Till then

const schema = z.object({
  meetingLink: z
    .string()
    .refine((value) => /^(https?):\/\/(?=.*\.[a-z]{2,})[^\s$.?#].[^\s]*$/i.test(value), {
      message: 'Please enter a valid URL',
    }),
});

this works for me

IvanVeljkovic avatar Jul 17 '24 11:07 IvanVeljkovic