Skip to content

Commit

Permalink
fix: generate types schemas not in reticular structure
Browse files Browse the repository at this point in the history
  • Loading branch information
C-ra-ZY committed Sep 2, 2024
1 parent d470e1c commit 07e0659
Show file tree
Hide file tree
Showing 7 changed files with 115 additions and 65 deletions.
5 changes: 5 additions & 0 deletions .changeset/smooth-panthers-drop.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"openapi-zod-client": patch
---

export all schemas types when --export-types === true
38 changes: 38 additions & 0 deletions lib/src/template-context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,44 @@ export const getZodClientTemplateContext = (
data.schemas[normalizeString(name)] = wrapWithLazyIfNeeded(name);
}

for (const schema in data.schemas) {
const ctx: TsConversionContext = { nodeByRef: {}, resolver: result.resolver, visitedsRefs: {} };
const shouldGenerateType = options?.shouldExportAllTypes;
const { ref, normalized } = result.resolver.resolveSchemaName(schema) ?? {};
const schemaName = shouldGenerateType ? normalized : undefined;
if (shouldGenerateType && schemaName && !data.types[schemaName]) {
const node = getTypescriptFromOpenApi({
schema: result.resolver.getSchemaByRef(ref),
ctx,
meta: { name: schemaName },
options,
}) as ts.Node;
data.types[schemaName] = printTs(node).replace("export ", "");
data.emittedType[schemaName] = true;

for (const depRef of depsGraphs.deepDependencyGraph[ref] ?? []) {
const depSchemaName = result.resolver.resolveRef(depRef).normalized;
const isDepCircular = depsGraphs.deepDependencyGraph[depRef]?.has(depRef);

if (!isDepCircular && !data.types[depSchemaName]) {
const nodeSchema = result.resolver.getSchemaByRef(depRef);
const node = getTypescriptFromOpenApi({
schema: nodeSchema,
ctx,
meta: { name: depSchemaName },
options,
}) as ts.Node;
data.types[depSchemaName] = printTs(node).replace("export ", "");
// defining types for strings and using the `z.ZodType<string>` type for their schema
// prevents consumers of the type from adding zod validations like `.min()` to the type
if (options?.shouldExportAllTypes && nodeSchema.type === "object") {
data.emittedType[depSchemaName] = true;
}
}
}
}
}

for (const ref in depsGraphs.deepDependencyGraph) {
const isCircular = ref && depsGraphs.deepDependencyGraph[ref]?.has(ref);
const ctx: TsConversionContext = { nodeByRef: {}, resolver: result.resolver, visitedsRefs: {} };
Expand Down
8 changes: 4 additions & 4 deletions lib/tests/allOf-infer-required-only-item.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,15 +73,15 @@ test("allOf-infer-required-only-item", async () => {
"import { makeApi, Zodios, type ZodiosOptions } from "@zodios/core";
import { z } from "zod";
type user = Partial<{
name: string;
email: string;
}>;
type userResponse = Partial<{
user: user & {
name: string;
};
}>;
type user = Partial<{
name: string;
email: string;
}>;
const user: z.ZodType<user> = z
.object({ name: z.string(), email: z.string() })
Expand Down
2 changes: 0 additions & 2 deletions lib/tests/anyOf-behavior.test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import { describe, test, expect } from "vitest";
import { z } from "zod";
import { makeSchemaResolver } from "../src/makeSchemaResolver.js";
import { getZodSchema } from "../src/openApiToZod.js";
import { asComponentSchema } from "../src/utils.js";
import { CodeMeta } from "../src/CodeMeta.js";
import { OpenAPIObject } from "openapi3-ts";
import { generateZodClientFromOpenAPI } from "../src/generateZodClientFromOpenAPI.js";
Expand Down
86 changes: 45 additions & 41 deletions lib/tests/enum-null.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,39 +12,39 @@ test("enum-null", async () => {
},
components: {
schemas: {
"Null1": {
Null1: {
type: "string",
enum: [null],
},
"Null2": {
Null2: {
type: "string",
enum: ["a", null],
},
"Null3": {
Null3: {
type: "string",
enum: ["a", null],
nullable: true
nullable: true,
},
"Null4": {
Null4: {
type: "string",
enum: [null],
nullable: true
nullable: true,
},
"Compound": {
Compound: {
type: "object",
properties: {
"field": {
field: {
oneOf: [
{ $ref: '#/components/schemas/Null1' },
{ $ref: '#/components/schemas/Null2' },
{ $ref: '#/components/schemas/Null3' },
{ $ref: '#/components/schemas/Null4' },
{ type: "string" }
]
}
}
}
}
{ $ref: "#/components/schemas/Null1" },
{ $ref: "#/components/schemas/Null2" },
{ $ref: "#/components/schemas/Null3" },
{ $ref: "#/components/schemas/Null4" },
{ type: "string" },
],
},
},
},
},
},
paths: {
"/sample": {
Expand All @@ -56,73 +56,77 @@ test("enum-null", async () => {
"application/json": {
schema: {
$ref: "#/components/schemas/Null1",
}
}
}
},
},
},
},
"400": {
description: "null with a string",
content: {
"application/json": {
schema: {
$ref: "#/components/schemas/Null2",
}
}
}
},
},
},
},
"401": {
description: "null with a string and nullable",
content: {
"application/json": {
schema: {
$ref: "#/components/schemas/Null3",
}
}
}
},
},
},
},
"402": {
description: "null with nullable",
content: {
"application/json": {
schema: {
$ref: "#/components/schemas/Null4",
}
}
}
},
},
},
},
"403": {
description: "object that references null",
content: {
"application/json": {
schema: {
$ref: "#/components/schemas/Compound",
}
}
}
},
},
},
},
},
},
},
},
};

const output = await generateZodClientFromOpenAPI({ disableWriteToFile: true, openApiDoc, options: { shouldExportAllTypes: true } });
const output = await generateZodClientFromOpenAPI({
disableWriteToFile: true,
openApiDoc,
options: { shouldExportAllTypes: true },
});
expect(output).toMatchInlineSnapshot(`
"import { makeApi, Zodios, type ZodiosOptions } from "@zodios/core";
import { z } from "zod";
type Compound = Partial<{
field: Null1 | Null2 | Null3 | Null4 | string;
}>;
type Null1 = null;
type Null2 = "a" | null;
type Null3 = "a" | null;
type Null4 = null;
type Compound = Partial<{
field: Null1 | Null2 | Null3 | Null4 | string;
}>;
const Null1 = z.literal(null);
const Null2 = z.enum(["a", null]);
const Null3 = z.enum(["a", null]);
const Null4 = z.literal(null);
const Null1: z.ZodType<Null1> = z.literal(null);
const Null2: z.ZodType<Null2> = z.enum(["a", null]);
const Null3: z.ZodType<Null3> = z.enum(["a", null]);
const Null4: z.ZodType<Null4> = z.literal(null);
const Compound: z.ZodType<Compound> = z
.object({ field: z.union([Null1, Null2, Null3, Null4, z.string()]) })
.partial()
Expand Down
35 changes: 19 additions & 16 deletions lib/tests/export-all-types.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -152,9 +152,12 @@ describe("export-all-types", () => {
endpointsGroups: {},
emittedType: {
Author: true,
Features: true,
Id: true,
Settings: true,
Playlist: true,
Song: true,
Title: true,
},
options: {
withAlias: false,
Expand All @@ -173,34 +176,34 @@ describe("export-all-types", () => {
"import { makeApi, Zodios, type ZodiosOptions } from "@zodios/core";
import { z } from "zod";
type Playlist = Partial<{
name: string;
author: Author;
songs: Array<Song>;
}> &
Settings;
type Title = string;
type Id = number;
type Features = Array<string>;
type Settings = Partial<{
theme_color: string;
features: Features;
}>;
type Author = Partial<{
name: (string | null) | number | null;
title: Title;
id: Id;
mail: string;
settings: Settings;
}>;
type Title = string;
type Id = number;
type Settings = Partial<{
theme_color: string;
features: Features;
}>;
type Features = Array<string>;
type Song = Partial<{
name: string;
duration: number;
}>;
type Playlist = Partial<{
name: string;
author: Author;
songs: Array<Song>;
}> &
Settings;
const Title = z.string();
const Id = z.number();
const Features = z.array(z.string());
const Title: z.ZodType<Title> = z.string();
const Id: z.ZodType<Id> = z.number();
const Features: z.ZodType<Features> = z.array(z.string());
const Settings: z.ZodType<Settings> = z
.object({ theme_color: z.string(), features: Features.min(1) })
.partial()
Expand Down
6 changes: 4 additions & 2 deletions lib/tests/recursive-schema.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -321,7 +321,9 @@ describe("recursive-schema", () => {
},
ctx,
})
).toMatchInlineSnapshot('"z.object({ recursiveUser: UserWithFriends, basic: z.number() }).partial().passthrough()"');
).toMatchInlineSnapshot(
'"z.object({ recursiveUser: UserWithFriends, basic: z.number() }).partial().passthrough()"'
);
expect(ctx).toMatchInlineSnapshot(`
{
"resolver": {
Expand All @@ -346,7 +348,7 @@ describe("recursive-schema", () => {
someProp: { type: "boolean" },
},
});

console.log(JSON.stringify(openApiDoc, null, 2));
expect(getZodiosEndpointDefinitionList(openApiDoc)).toMatchInlineSnapshot(`
{
"deepDependencyGraph": {
Expand Down

0 comments on commit 07e0659

Please sign in to comment.