#Transforming a value with Zod to be a URL or null

5 messages · Page 1 of 1 (latest)

long kite
#

I'm calling an API which uses empty strings instead of null or a result object or anything better, and I don't want empty strings to leak into the codebase. I am trying to make Zod transform these empty strings into an instance of a URL or null.

import { z } from 'astro/zod';

export const authorSchema = z.object({
    type: z.string(),
    name: z.string(),
    photo: z.string(),
    // url: z.string()
    //     .refine((val) => val === '' || z.string().url().safeParse(val).success)
    //     .transform((val) => (val === '' ? null : val)),
    url: z.string()
        .transform((val) => {
            if (val === '') return null;
            try {
                return new URL(val);
            } catch {
                return null;
            }
        })
        .refine((val) => val === null || val instanceof URL)
});

And these are the tests I have:

        const emptyAuthor = authorSchema.safeParse({
            type: 'card',
            name: '',
            url: '',
            photo: '',
        });

        const goodAuthor = authorSchema.safeParse({
            type: 'card',
            name: faker.person.firstName(),
            url: new URL(faker.internet.url()),
            photo: faker.image.avatar(),
        });

        it('transforms empty string url to null and validates non-empty urls', () => {
            expect(emptyAuthor.success).toBe(true);
            expect(emptyAuthor.data?.url).toBe(null);
        });

        it('transforms non-empty string url to url and validates the urls', () => {
            console.log(goodAuthor.error?.errors);
            expect(goodAuthor.success).toBe(true);
             expect(goodAuthor.data?.url).toBeInstanceOf(URL);
        });

However this fails with:

[
  {
    code: 'invalid_type',
    expected: 'string',
    received: 'object',
    path: [ 'url' ],
    message: 'Expected string, received object'
  }
]

...

AssertionError: expected false to be true // Object.is equality

- Expected
+ Received

- true
+ false

 ❯ design/article/webmentions/webmentions.spec.ts:72:40
     70|         it('transforms non-empty string url to url and validates the urls', () => {
     71|             console.log(goodAuthor.error?.errors);
     72|             expect(goodAuthor.success).toBe(true);

I tried a couple of different approaches for the URL but I keep coming back to the same problems:

  • Zod returns a string
  • Can't seem to get an actual URL, closest I come to is an object but it fails the instanceof check
#

I'm able to get the first part working - returning a null if its an empty string, but the second part of making it a URL when its not an empty string is tripping me up

uneven sluice
#

Assuming faker.internet.url() produces a string

long kite
#

ugh I can't believe it was that - a wrong test setup