Skip to content

Commit dd361cc

Browse files
committed
feat: options.complexityThreshold
1 parent ed3e320 commit dd361cc

20 files changed

+665
-72
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -229,7 +229,7 @@ const endpoints = makeApi([
229229
schema: z.number().optional(),
230230
},
231231
],
232-
response: Pets,
232+
response: z.array(Pet),
233233
},
234234
{
235235
method: "post",

src/CodeMeta.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { ReferenceObject, SchemaObject } from "openapi3-ts";
22
import { isReferenceObject } from "openapi3-ts";
33

4+
import { getSchemaComplexity } from "./schema-complexity";
45
import { getRefName } from "./utils";
56

67
export type ConversionTypeContext = {
@@ -51,6 +52,10 @@ export class CodeMeta {
5152
return getRefName(this.ref!);
5253
}
5354

55+
get complexity(): number {
56+
return getSchemaComplexity({ current: 0, schema: this.schema });
57+
}
58+
5459
assign(code: string) {
5560
this.code = code;
5661

src/cli.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@ cli.command("<input>", "path/url to OpenAPI/Swagger document as json/yaml")
3434
"--group-strategy",
3535
"groups endpoints by a given strategy, possible values are: 'none' | 'tag' | 'method' | 'tag-file' | 'method-file'"
3636
)
37+
.option(
38+
"--complexity-threshold",
39+
"schema complexity threshold to determine which one (using less than `<` operator) should be assigned to a variable"
40+
)
3741
.action(async (input, options) => {
3842
console.log("Retrieving OpenAPI document from", input);
3943
const openApiDoc = (await SwaggerParser.bundle(input)) as OpenAPIObject;
@@ -55,6 +59,7 @@ cli.command("<input>", "path/url to OpenAPI/Swagger document as json/yaml")
5559
withImplicitRequiredProps: options.implicitRequired,
5660
withDeprecatedEndpoints: options.withDeprecated,
5761
groupStrategy: options.groupStrategy,
62+
complexityThreshold: options.complexityThreshold,
5863
},
5964
});
6065
console.log(`Done generating <${distPath}> !`);

src/generateZodClientFromOpenAPI.test.ts

Lines changed: 12 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ test("getZodClientTemplateContext", async () => {
133133
"parameters": [
134134
{
135135
"name": "status",
136-
"schema": "status",
136+
"schema": "z.enum(["available", "pending", "sold"]).optional()",
137137
"type": "Query",
138138
},
139139
],
@@ -155,7 +155,7 @@ test("getZodClientTemplateContext", async () => {
155155
"parameters": [
156156
{
157157
"name": "tags",
158-
"schema": "tags",
158+
"schema": "z.array(z.string()).optional()",
159159
"type": "Query",
160160
},
161161
],
@@ -298,7 +298,7 @@ test("getZodClientTemplateContext", async () => {
298298
{
299299
"description": undefined,
300300
"name": "body",
301-
"schema": "createUsersWithListInput_Body",
301+
"schema": "z.array(User)",
302302
"type": "Body",
303303
},
304304
],
@@ -356,9 +356,6 @@ test("getZodClientTemplateContext", async () => {
356356
"Pet": "z.object({ id: z.number().int().optional(), name: z.string(), category: Category.optional(), photoUrls: z.array(z.string()), tags: z.array(Tag).optional(), status: z.enum(["available", "pending", "sold"]).optional() })",
357357
"Tag": "z.object({ id: z.number().int(), name: z.string() }).partial()",
358358
"User": "z.object({ id: z.number().int(), username: z.string(), firstName: z.string(), lastName: z.string(), email: z.string(), password: z.string(), phone: z.string(), userStatus: z.number().int() }).partial()",
359-
"createUsersWithListInput_Body": "z.array(User)",
360-
"status": "z.enum(["available", "pending", "sold"]).optional()",
361-
"tags": "z.array(z.string()).optional()",
362359
},
363360
"types": {},
364361
}
@@ -382,8 +379,6 @@ describe("generateZodClientFromOpenAPI", () => {
382379
tags: z.array(Tag).optional(),
383380
status: z.enum(["available", "pending", "sold"]).optional(),
384381
});
385-
const status = z.enum(["available", "pending", "sold"]).optional();
386-
const tags = z.array(z.string()).optional();
387382
const ApiResponse = z
388383
.object({ code: z.number().int(), type: z.string(), message: z.string() })
389384
.partial();
@@ -409,7 +404,6 @@ describe("generateZodClientFromOpenAPI", () => {
409404
userStatus: z.number().int(),
410405
})
411406
.partial();
412-
const createUsersWithListInput_Body = z.array(User);
413407
414408
const endpoints = makeApi([
415409
{
@@ -519,7 +513,7 @@ describe("generateZodClientFromOpenAPI", () => {
519513
{
520514
name: "status",
521515
type: "Query",
522-
schema: status,
516+
schema: z.enum(["available", "pending", "sold"]).optional(),
523517
},
524518
],
525519
response: z.array(Pet),
@@ -540,7 +534,7 @@ describe("generateZodClientFromOpenAPI", () => {
540534
{
541535
name: "tags",
542536
type: "Query",
543-
schema: tags,
537+
schema: z.array(z.string()).optional(),
544538
},
545539
],
546540
response: z.array(Pet),
@@ -675,7 +669,7 @@ describe("generateZodClientFromOpenAPI", () => {
675669
{
676670
name: "body",
677671
type: "Body",
678-
schema: createUsersWithListInput_Body,
672+
schema: z.array(User),
679673
},
680674
],
681675
response: User,
@@ -738,8 +732,6 @@ describe("generateZodClientFromOpenAPI", () => {
738732
tags: z.array(Tag).optional(),
739733
status: z.enum(["available", "pending", "sold"]).optional(),
740734
});
741-
const status = z.enum(["available", "pending", "sold"]).optional();
742-
const tags = z.array(z.string()).optional();
743735
const ApiResponse = z
744736
.object({ code: z.number().int(), type: z.string(), message: z.string() })
745737
.partial();
@@ -765,7 +757,6 @@ describe("generateZodClientFromOpenAPI", () => {
765757
userStatus: z.number().int(),
766758
})
767759
.partial();
768-
const createUsersWithListInput_Body = z.array(User);
769760
770761
const endpoints = makeApi([
771762
{
@@ -880,7 +871,7 @@ describe("generateZodClientFromOpenAPI", () => {
880871
{
881872
name: "status",
882873
type: "Query",
883-
schema: status,
874+
schema: z.enum(["available", "pending", "sold"]).optional(),
884875
},
885876
],
886877
response: z.array(Pet),
@@ -902,7 +893,7 @@ describe("generateZodClientFromOpenAPI", () => {
902893
{
903894
name: "tags",
904895
type: "Query",
905-
schema: tags,
896+
schema: z.array(z.string()).optional(),
906897
},
907898
],
908899
response: z.array(Pet),
@@ -1044,7 +1035,7 @@ describe("generateZodClientFromOpenAPI", () => {
10441035
{
10451036
name: "body",
10461037
type: "Body",
1047-
schema: createUsersWithListInput_Body,
1038+
schema: z.array(User),
10481039
},
10491040
],
10501041
response: User,
@@ -1111,8 +1102,6 @@ describe("generateZodClientFromOpenAPI", () => {
11111102
tags: z.array(Tag).optional(),
11121103
status: z.enum(["available", "pending", "sold"]).optional(),
11131104
});
1114-
const status = z.enum(["available", "pending", "sold"]).optional();
1115-
const tags = z.array(z.string()).optional();
11161105
const ApiResponse = z
11171106
.object({ code: z.number().int(), type: z.string(), message: z.string() })
11181107
.partial();
@@ -1138,7 +1127,6 @@ describe("generateZodClientFromOpenAPI", () => {
11381127
userStatus: z.number().int(),
11391128
})
11401129
.partial();
1141-
const createUsersWithListInput_Body = z.array(User);
11421130
11431131
const endpoints = makeApi([
11441132
{
@@ -1248,7 +1236,7 @@ describe("generateZodClientFromOpenAPI", () => {
12481236
{
12491237
name: "status",
12501238
type: "Query",
1251-
schema: status,
1239+
schema: z.enum(["available", "pending", "sold"]).optional(),
12521240
},
12531241
],
12541242
response: z.array(Pet),
@@ -1269,7 +1257,7 @@ describe("generateZodClientFromOpenAPI", () => {
12691257
{
12701258
name: "tags",
12711259
type: "Query",
1272-
schema: tags,
1260+
schema: z.array(z.string()).optional(),
12731261
},
12741262
],
12751263
response: z.array(Pet),
@@ -1404,7 +1392,7 @@ describe("generateZodClientFromOpenAPI", () => {
14041392
{
14051393
name: "body",
14061394
type: "Body",
1407-
schema: createUsersWithListInput_Body,
1395+
schema: z.array(User),
14081396
},
14091397
],
14101398
response: User,

src/getZodiosEndpointDefinitionList.test.ts

Lines changed: 7 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -418,7 +418,7 @@ test("getZodiosEndpointDefinitionFromOpenApiDoc /pet/findXXX", () => {
418418
"parameters": [
419419
{
420420
"name": "status",
421-
"schema": "status",
421+
"schema": "z.enum(["available", "pending", "sold"]).optional()",
422422
"type": "Query",
423423
},
424424
],
@@ -440,7 +440,7 @@ test("getZodiosEndpointDefinitionFromOpenApiDoc /pet/findXXX", () => {
440440
"parameters": [
441441
{
442442
"name": "tags",
443-
"schema": "tags",
443+
"schema": "z.array(z.string()).optional()",
444444
"type": "Query",
445445
},
446446
],
@@ -456,16 +456,11 @@ test("getZodiosEndpointDefinitionFromOpenApiDoc /pet/findXXX", () => {
456456
"#/components/schemas/Tag",
457457
},
458458
},
459-
"schemaByName": {
460-
"z.array(z.string()).optional()": "tags",
461-
"z.enum(["available", "pending", "sold"]).optional()": "status",
462-
},
459+
"schemaByName": {},
463460
"zodSchemaByName": {
464461
"Category": "z.object({ id: z.number().int(), name: z.string() }).partial()",
465462
"Pet": "z.object({ id: z.number().int().optional(), name: z.string(), category: Category.optional(), photoUrls: z.array(z.string()), tags: z.array(Tag).optional(), status: z.enum(["available", "pending", "sold"]).optional() })",
466463
"Tag": "z.object({ id: z.number().int(), name: z.string() }).partial()",
467-
"status": "z.enum(["available", "pending", "sold"]).optional()",
468-
"tags": "z.array(z.string()).optional()",
469464
},
470465
}
471466
`);
@@ -553,7 +548,7 @@ test("petstore.yaml", async () => {
553548
"parameters": [
554549
{
555550
"name": "status",
556-
"schema": "status",
551+
"schema": "z.enum(["available", "pending", "sold"]).optional()",
557552
"type": "Query",
558553
},
559554
],
@@ -575,7 +570,7 @@ test("petstore.yaml", async () => {
575570
"parameters": [
576571
{
577572
"name": "tags",
578-
"schema": "tags",
573+
"schema": "z.array(z.string()).optional()",
579574
"type": "Query",
580575
},
581576
],
@@ -803,7 +798,7 @@ test("petstore.yaml", async () => {
803798
{
804799
"description": undefined,
805800
"name": "body",
806-
"schema": "createUsersWithListInput_Body",
801+
"schema": "z.array(User)",
807802
"type": "Body",
808803
},
809804
],
@@ -932,21 +927,14 @@ test("petstore.yaml", async () => {
932927
"#/components/schemas/Tag",
933928
},
934929
},
935-
"schemaByName": {
936-
"z.array(User)": "createUsersWithListInput_Body",
937-
"z.array(z.string()).optional()": "tags",
938-
"z.enum(["available", "pending", "sold"]).optional()": "status",
939-
},
930+
"schemaByName": {},
940931
"zodSchemaByName": {
941932
"ApiResponse": "z.object({ code: z.number().int(), type: z.string(), message: z.string() }).partial()",
942933
"Category": "z.object({ id: z.number().int(), name: z.string() }).partial()",
943934
"Order": "z.object({ id: z.number().int(), petId: z.number().int(), quantity: z.number().int(), shipDate: z.string(), status: z.enum(["placed", "approved", "delivered"]), complete: z.boolean() }).partial()",
944935
"Pet": "z.object({ id: z.number().int().optional(), name: z.string(), category: Category.optional(), photoUrls: z.array(z.string()), tags: z.array(Tag).optional(), status: z.enum(["available", "pending", "sold"]).optional() })",
945936
"Tag": "z.object({ id: z.number().int(), name: z.string() }).partial()",
946937
"User": "z.object({ id: z.number().int(), username: z.string(), firstName: z.string(), lastName: z.string(), email: z.string(), password: z.string(), phone: z.string(), userStatus: z.number().int() }).partial()",
947-
"createUsersWithListInput_Body": "z.array(User)",
948-
"status": "z.enum(["available", "pending", "sold"]).optional()",
949-
"tags": "z.array(z.string()).optional()",
950938
},
951939
}
952940
`);

src/getZodiosEndpointDefinitionList.ts

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { sync } from "whence";
1616
import type { CodeMeta, ConversionTypeContext } from "./CodeMeta";
1717
import { getOpenApiDependencyGraph } from "./getOpenApiDependencyGraph";
1818
import { getZodChainablePresence, getZodSchema } from "./openApiToZod";
19+
import { getSchemaComplexity } from "./schema-complexity";
1920
import type { TemplateContext } from "./template-context";
2021
import { getRefFromName, normalizeString, pathToVariableName } from "./utils";
2122

@@ -53,19 +54,24 @@ export const getZodiosEndpointDefinitionList = (doc: OpenAPIObject, options?: Te
5354
}
5455

5556
const ctx: ConversionTypeContext = { getSchemaByRef, zodSchemaByName: {}, schemaByName: {} };
57+
const complexityThreshold = options?.complexityThreshold ?? 4;
5658
const getZodVarName = (input: CodeMeta, fallbackName?: string) => {
5759
const result = input.toString();
5860

61+
// special value, inline everything (= no variable used)
62+
if (complexityThreshold === -1) {
63+
return input.ref ? ctx.zodSchemaByName[result]! : result;
64+
}
65+
5966
if (result.startsWith("z.") && fallbackName) {
6067
// result is simple enough that it doesn't need to be assigned to a variable
61-
if (!complexType.some((type) => result.startsWith(type))) {
62-
// if (input.complexity < 10) {
68+
if (input.complexity < complexityThreshold) {
6369
return result;
6470
}
6571

6672
const safeName = normalizeString(fallbackName);
6773

68-
// if schema is already assigned to a variable, re-use that variable
74+
// if schema is already assigned to a variable, re-use that variable name
6975
if (ctx.schemaByName[result]) {
7076
return ctx.schemaByName[result]!;
7177
}
@@ -87,7 +93,14 @@ export const getZodiosEndpointDefinitionList = (doc: OpenAPIObject, options?: Te
8793
}
8894

8995
// result is a reference to another schema
90-
if (ctx.zodSchemaByName[result]) {
96+
if (input.ref && ctx.zodSchemaByName[result]) {
97+
const complexity = getSchemaComplexity({ current: 0, schema: getSchemaByRef(input.ref) });
98+
99+
// ref result is simple enough that it doesn't need to be assigned to a variable
100+
if (complexity < complexityThreshold) {
101+
return ctx.zodSchemaByName[result]!;
102+
}
103+
91104
return result;
92105
}
93106

@@ -186,7 +199,6 @@ export const getZodiosEndpointDefinitionList = (doc: OpenAPIObject, options?: Te
186199

187200
if (isMainResponseStatus(status) || (statusCode === "default" && !endpointDescription.response)) {
188201
endpointDescription.response = schemaString;
189-
// console.log({ schema, schemaString });
190202

191203
if (
192204
!endpointDescription.description &&
@@ -235,5 +247,4 @@ export type EndpointDescriptionWithRefs = Omit<
235247
errors: Array<Omit<Required<ZodiosEndpointDefinition<any>>["errors"][number], "schema"> & { schema: string }>;
236248
};
237249

238-
const complexType = ["z.object", "z.array", "z.union", "z.enum"] as const;
239250
const pathParamRegex = /{(\w+)}/g;

0 commit comments

Comments
 (0)