astahmer / openapi-zod-client

Generate a zodios (typescript http client with zod validation) from an OpenAPI spec (json/yaml)

Home Page:openapi-zod-client.vercel.app

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

anyOf not handled correctly

WickyNilliams opened this issue · comments

hey, i think i have come across an issue with the zod schema generated for anyOf.

consider the example given in the swagger docs: https://swagger.io/docs/specification/data-models/oneof-anyof-allof-not/#anyof

it lists a number of valid and invalid requests. the last of the valid requests is like this:

{
  "nickname": "Fido",
  "pet_type": "Dog",
  "age": 4
}

with the given openapi schema, the generated zod schema looks like this:

const patchPets_Body = z.union([
  z.union([PetByAge, PetByType]),
  z.array(z.union([PetByAge, PetByType])),
]);

but when parsed, the pet_type key gets stripped.

looking at the swagger docs, i don't think array should be part of this. in this simple example, might this be correct?

const patchPets_BodyFixed = z.union([
  z.intersection(PetByAge, PetByType), // it seems important to zod that this comes first?
  PetByAge,
  PetByType,
]);

effectively, (A & B) | A | B (proposed) instead of A | B | Array<A | B> (current)

i'm not sure if this gets more complicated when there are more than two anyOf options?

Here's a link to the playground

And here's a stackblitz where i've copied the types from the playground and also applied my possible fix. you can see the fix works as described in the swagger docs, whereas the current output does not https://stackblitz.com/edit/typescript-peyk8j?file=index.ts

hey, feel free to open a PR about this 🙏

to have the same output as you described, you could replace those lines

const oneOf = `z.union([${types}])`;
return code.assign(`z.union([${oneOf}, z.array(${oneOf})])`);

- const oneOf = `z.union([${types}])`;
- return code.assign(`z.union([${oneOf}, z.array(${oneOf})])`);
+ return code.assign(`z.union([z.intersection(${types}), z.array(z.union([${types}]))])`);

and then I'll let you handle the cases where there are more than 2 items
also, please make a dedicated test case / check that the others are still fine and add a changeset

having looked into this a bit more, i think the behavior of anyOf is a little more subtle than i first thought. i've dug up a load of test cases from json-schema (from which openapi inherits anyOf). i'll go through them next week and try to nail down the behavior. i'm actually not 100% sure the types are representable in TS or zod. but i will report back, and land a PR if possible 👍

i'm having an issue fetching the samples for tests.

when i run pnpm gen as per the readme, i get:

❯ pnpm gen
 ERR_PNPM_RECURSIVE_EXEC_FIRST_FAIL  Command failed with ENOENT: gen
spawn gen ENOENT

Command "gen" not found.

i'm not familiar with pnpm, seems i can do wildcard commands with regex, so trying that:

❯ pnpm run /gen/

> openapi-zod-client-monorepo@1.4.18 gen:samples /path/to/openapi-zod-client
> rm -rf ./samples && tsx ./lib/samples-generator.ts

(node:18485) ExperimentalWarning: Custom ESM Loaders is an experimental feature and might change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
/path/to/openapi-zod-client/node_modules/.pnpm/degit@2.8.4/node_modules/degit/dist/index-688c5d50.js:14258
			throw new DegitError(`could not find commit hash for ${repo.ref}`, {
			      ^


DegitError: could not find commit hash for HEAD

any idea what the issue might be?

Screenshot 2023-06-05 at 17 10 16

uh, no idea actually, it seems fine for me when running pnpm gen:samples from the root

hmm weird! maybe something to do with node version?

in any case, i just restored those files in git and decided not to generate afresh, sidestepping that problem.

will make a PR shortly