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

fix: generate types schemas not in reticular structure #307

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
50 changes: 41 additions & 9 deletions lib/src/template-context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,15 +53,18 @@ export const getZodClientTemplateContext = (
data.schemas[normalizeString(name)] = wrapWithLazyIfNeeded(name);
}

for (const ref in depsGraphs.deepDependencyGraph) {
const isCircular = ref && depsGraphs.deepDependencyGraph[ref]?.has(ref);
const ctx: TsConversionContext = { nodeByRef: {}, resolver: result.resolver, visitedsRefs: {} };

// Specifically check isCircular if shouldExportAllTypes is false. Either should cause shouldGenerateType to be true.
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
const shouldGenerateType = options?.shouldExportAllTypes || isCircular;
const schemaName = shouldGenerateType ? result.resolver.resolveRef(ref).normalized : undefined;
if (shouldGenerateType && schemaName && !data.types[schemaName]) {
const getTypescript = ({
ctx,
ref,
shouldGenerateType,
schemaName,
}: {
ctx: TsConversionContext;
ref?: string;
shouldGenerateType: boolean;
schemaName?: string | undefined;
}) => {
if (ref && shouldGenerateType && schemaName && !data.types[schemaName]) {
const node = getTypescriptFromOpenApi({
schema: result.resolver.getSchemaByRef(ref),
ctx,
Expand Down Expand Up @@ -92,6 +95,35 @@ export const getZodClientTemplateContext = (
}
}
}
};

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;
getTypescript({
ctx,
ref,
shouldGenerateType,
schemaName,
});
}

for (const ref in depsGraphs.deepDependencyGraph) {
const isCircular = ref && depsGraphs.deepDependencyGraph[ref]?.has(ref);
const ctx: TsConversionContext = { nodeByRef: {}, resolver: result.resolver, visitedsRefs: {} };

// Specifically check isCircular if shouldExportAllTypes is false. Either should cause shouldGenerateType to be true.
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
const shouldGenerateType = !!(options?.shouldExportAllTypes || isCircular);
const schemaName = shouldGenerateType ? result.resolver.resolveRef(ref).normalized : undefined;
getTypescript({
ctx,
ref,
shouldGenerateType,
schemaName,
});
}

// TODO
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: 3 additions & 3 deletions lib/tests/jsdoc.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,9 @@ test("jsdoc", async () => {
expect(output).toMatchInlineSnapshot(`"import { makeApi, Zodios, type ZodiosOptions } from "@zodios/core";
import { z } from "zod";

type SimpleObject = Partial<{
str: string;
}>;
type ComplexObject = Partial<{
/**
* A string with example tag
Expand Down Expand Up @@ -156,9 +159,6 @@ type ComplexObject = Partial<{
*/
refArray: Array<SimpleObject>;
}>;
type SimpleObject = Partial<{
str: string;
}>;

const SimpleObject: z.ZodType<SimpleObject> = z
.object({ str: z.string() })
Expand Down
5 changes: 3 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,6 @@ describe("recursive-schema", () => {
someProp: { type: "boolean" },
},
});

expect(getZodiosEndpointDefinitionList(openApiDoc)).toMatchInlineSnapshot(`
{
"deepDependencyGraph": {
Expand Down
Loading