Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Generating a schema from a type with a nullable object causes an error. (v3.7.0) #203

Closed
yumemi-tokumoto opened this issue Feb 13, 2024 · 10 comments · Fixed by #204
Closed
Assignees

Comments

@yumemi-tokumoto
Copy link

Bug description

Error occurs and schema generation fails.
Generating a schema from a type with a nullable object causes an error. (v3.7.0)

Input

export type User = {
  name: string
}

export type Article = {
  user: User | null
}

Expected output

export const userSchema = z.object({
  name: z.string(),
})

export const articleSchema = z.object({
  user: userSchema.nullable(),
})

Actual output

✖ Validating generated types
 ›   Error: 'Article' is not compatible with 'articleSchema':
 ›   Argument of type 'Article' is not assignable to parameter of type '{ user: { name: string; } | null; }'.
 ›     Types of property 'user' are incompatible.
 ›       Type 'User | null | undefined' is not assignable to type '{ name: string; } | null'.
 ›         Type 'undefined' is not assignable to type '{ name: string; } | null'.

Versions

  • Typescript: v5.3.3
  • Zod: v3.22.4
@schiller-manuel
Copy link
Collaborator

did this work with previous versions of ts-to-zod?

@yumemi-tokumoto
Copy link
Author

yumemi-tokumoto commented Feb 14, 2024

When I was using version 3.6.1, I was able to generate it without error.

@schiller-manuel
Copy link
Collaborator

@tvillaren can you have a look please? seems like your latest changes introduced a regression.

@adamwdennis
Copy link

I am also getting this error.

@tvillaren
Copy link
Collaborator

tvillaren commented Feb 15, 2024

@tvillaren can you have a look please? seems like your latest changes introduced a regression.

I'll have a look.
You can assign the ticket to me 😄 (I don't have any rights on the project)

@schiller-manuel
Copy link
Collaborator

This can be reproduced by running

$ yarn gen:example

output:

yarn run v1.22.19
warning ../../package.json: No license field
$ ./bin/run --config example
- Validating generated types
✖ Validating generated types
 ›   Error: 'SkillsSpeedEnemy' is not compatible with 'skillsSpeedEnemySchema':
 ›   Argument of type 'SkillsSpeedEnemy' is not assignable to parameter of type
 ›    '{ power: EnemyPower.Speed; }'.
 ›     Types of property 'power' are incompatible.
 ›       Type 'EnemyPower.Speed | undefined' is not assignable to type 
 ›   'EnemyPower.Speed'.
 ›         Type 'undefined' is not assignable to type 'EnemyPower.Speed'.
 ›   'Superman' is not compatible with 'supermanSchema':
 ›   Argument of type 'Superman' is not assignable to parameter of type '{ 
 ›   name: "superman" | "clark kent" | "kal-l"; enemies: Record<string, any>; 
 ›   age: number; powers: ["fly", "laser", "invincible"]; underKryptonite?: 
 ›   boolean | undefined; }'.
 ›     Types of property 'enemies' are incompatible.
 ›       Type 'Record<string, Enemy> | undefined' is not assignable to type 
 ›   'Record<string, any>'.
 ›         Type 'undefined' is not assignable to type 'Record<string, any>'.
 ›   'Exported' is not compatible with 'exportedSchema':
 ›   Argument of type 'Exported' is not assignable to parameter of type '{ a: {
 ›    name: string; }; b: string; }'.
 ›     Types of property 'a' are incompatible.
 ›       Type 'NonExported | undefined' is not assignable to type '{ name: 
 ›   string; }'.
 ›         Type 'undefined' is not assignable to type '{ name: string; }'.
error Command failed with exit code 2.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.

I see 3 separate issues:

  1. most obvious: why didn't our tests find this?
  2. | undefined is somehow added to referenced types in the validation
  3. if a type is imported from a separate file, it correctly generates the type but during validation this separate file is not imported; see Record<string, any> above which should be Record<string, Enemy>;

@tvillaren do you typically use --skipValidation?

@tvillaren
Copy link
Collaborator

tvillaren commented Feb 15, 2024

Yes, the use case is specific to TypeReference being set as optional by fixOptionalAny during the validation step.

This was introduced for any keyword due a known issue with ts-to-zod (see #140) but I updated this method to handle external imports (which are inferred as any as well in our validation step).

Pushed a failing test to reproduce: main...tvillaren:ts-to-zod:fix-issue-203
The issue is that we now mark all TypeReference as optional while we should do so only for imported ones...

We were missing test to cover use cases before my addition of import handling so it didn't catch it.

@mpiltz
Copy link

mpiltz commented Feb 19, 2024

We have run in to this same issues when we are generating TS Type Definitions from OpenAPI Schemas and then genarate zod-schemas from the generated TS types and interfaces.

TL;DR; zod-schema generation fails with same error from this:

export type MyField = string
export interface MyObject {
 myField: MyField
}

schiller-manuel pushed a commit that referenced this issue Feb 19, 2024
* test: adding failing test-case

* test: fix test syntax

* refacto: reorganise file

* fix: unit test passes

* clean: prettying

* test: adding more test cases

* doc: adding comment
@johnnyp-gg
Copy link

johnnyp-gg commented May 10, 2024

Im having the same issue with:

Input

export type User = {
  username: string | undefined
}

Expected output

export const userSchema = z.object({
  name: z.string().optional(),
})

on version 3.8.3 could it need the same fix?

@tvillaren
Copy link
Collaborator

@johnnyp-gg: actually

export type User = {
  username: string | undefined
}

generates (with --skipValidation flag)

// Generated by ts-to-zod
import { z } from "zod";

export const userSchema = z.object({
  username: z.union([z.string(), z.undefined()]),
});

which is the expected schema.
Indeed, it's not the same to check for an optional field and for a mandatory one which may take the undefined value.
The validation error you're getting comes from a known bug in Zod inference (colinhacks/zod#2464)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

6 participants