chrishoermann / zod-prisma-types

Generator creates zod types for your prisma models with advanced validation
Other
626 stars 47 forks source link

Optional field with default generates required field #103

Closed Brenndoerfer closed 1 year ago

Brenndoerfer commented 1 year ago

When the Prisma model defines an optional value, for example:

max_ping_timeout Int? then the Zod schema will be z.number().int().nullable(),

However, if you define a default for the optional field, e.g. max_ping_timeout Int? @default(5), the generated zod schema is z.number().int(), not nullable.

I believe this is not the intended behavior, as a default on an optional field is a sensible default, not a requirement.

I am on "zod-prisma-types": "^2.0.12",

chrishoermann commented 1 year ago

@Brenndoerfer actutally you're only kind of right. :wink: The generator only reflects the types from prisma's index.d.ts.

CreateInput type

So let's say we have the following model:

model WithDefault {
  id        Int    @id @default(autoincrement())
  default   String @default("default")
  noDefault String
}

According to this model the default field should be optional when creating an entry . If we look in the index.d.ts the createInputType does reflect this:

export type WithDefaultCreateInput = {
  default?: string
  noDefault: string
}

The corresponding zod schema also reflects this optional field

export const WithDefaultCreateInputSchema: z.ZodType<Prisma.WithDefaultCreateInput> =
  z
    .object({
      default: z.string().optional(),
      noDefault: z.string(),
    })
    .strict();

Model type

This is different in the model type. In the index.d.ts the type of the model looks like this:

export type WithDefault = {
  id: number
  default: string
  noDefault: string
}

and the schema also reflects this:

export const WithDefaultSchema = z.object({
  id: z.number().int(),
  default: z.string(),
  noDefault: z.string(),
})

so here the default field is not optional. This is because the model type is the one that is used when retrieving an element from the database and there the default field should never be undefined or null.

Solutions

So if you just need the type when creating an element, I'd suggest to use the corresponding CreateInputType. If you need a field of the model type to be optional you'd have to use a workaround like this:

const PickedType = WithDefaultSchema.omit({
  default: true,
}).merge(
  z.object({
    default: z.string().optional(),
  }),
);

// The type would be:

type PickedType = {
    default?: string | undefined;
    id: number;
    noDefault: string;
}

Hope this helps.

chrishoermann commented 1 year ago

@Brenndoerfer acutally there is the option createOptionalDefaultValuesTypes = true in the generator config (that I totally forgot about) that generates exactly the model schema you need. See the docs for more info. Maybe you need to upgrade the version of the generator bc. I can't remember when this option has been added.

Brenndoerfer commented 1 year ago

Thanks for the explanation. Interestingly, I noticed that I had createOptionalDefaultValuesTypes already set to true. Will debug a bit more, ant report back soon. Thanks for the elaborate response!