Skip to content

Commit 951cd75

Browse files
authored
Merge pull request #34 from poviolabs/feature/custom-zod-brand
Refactor zod utils generation & Add brands
2 parents 29c58ba + e3e30d5 commit 951cd75

22 files changed

Lines changed: 278 additions & 110 deletions

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@povio/openapi-codegen-cli",
3-
"version": "0.13.11",
3+
"version": "0.14.0",
44
"main": "./dist/index.js",
55
"bin": {
66
"openapi-codegen": "./dist/sh.js"
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { OpenAPIV3 } from "openapi-types";
2+
3+
const DATETIME_SCHEMA: OpenAPIV3.SchemaObject = { type: "string", format: "date-time" };
4+
5+
const EMAIL_SCHEMA: OpenAPIV3.SchemaObject = { type: "string", format: "email" };
6+
7+
const DATE_RANGE_SCHEMA: OpenAPIV3.SchemaObject = {
8+
type: "object",
9+
properties: {
10+
start: { type: "string", format: "date-time" },
11+
end: { type: "string", format: "date-time" },
12+
},
13+
};
14+
15+
const TEXT_EDITOR_SCHEMA: OpenAPIV3.SchemaObject = {
16+
type: "object",
17+
properties: {
18+
html: { type: "string" },
19+
json: { type: "object" },
20+
},
21+
};
22+
23+
export enum BrandEnum {
24+
datetime = "datetime",
25+
email = "email",
26+
dateRange = "dateRange",
27+
textEditor = "textEditor",
28+
}
29+
30+
export const BRANDS: Record<BrandEnum, OpenAPIV3.SchemaObject> = {
31+
[BrandEnum.datetime]: DATETIME_SCHEMA,
32+
[BrandEnum.email]: EMAIL_SCHEMA,
33+
[BrandEnum.dateRange]: DATE_RANGE_SCHEMA,
34+
[BrandEnum.textEditor]: TEXT_EDITOR_SCHEMA,
35+
};

src/generators/const/deps.const.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -74,12 +74,13 @@ export const MUTATION_EFFECTS = {
7474
};
7575
export const MUTATION_EFFECTS_FILE: GenerateFile = { fileName: "useMutationEffects", extension: "ts" };
7676

77-
// ZodExtended
78-
export const ZOD_EXTENDED = {
79-
name: "zod",
80-
properties: {
81-
sortingString: "sortingString",
77+
// ZodUtils
78+
export const ZOD_UTILS = {
79+
namespace: "ZodUtils",
80+
exports: {
8281
parse: "parse",
82+
sortExp: "sortExp",
83+
brand: "brand",
8384
},
8485
};
85-
export const ZOD_EXTENDED_FILE: GenerateFile = { fileName: "zod", extension: "ts" };
86+
export const ZOD_UTILS_FILE: GenerateFile = { fileName: "zod.utils", extension: "ts" };

src/generators/core/zod/getZodChain.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ export function getZodChain({
1616
const chains: string[] = [];
1717

1818
match(schema.type)
19-
.with("string", () => chains.push(getZodChainableStringValidations(schema, options)))
19+
.with("string", () => chains.push(getZodChainableStringValidations(schema)))
2020
.with("number", "integer", () => chains.push(getZodChainableNumberValidations(schema)))
2121
.with("array", () => chains.push(getZodChainableArrayValidations(schema)))
2222
.otherwise(() => void 0);
@@ -75,7 +75,7 @@ function getZodChainableDefault(schema: OpenAPIV3.SchemaObject) {
7575
return "";
7676
}
7777

78-
function getZodChainableStringValidations(schema: OpenAPIV3.SchemaObject, options: GenerateOptions) {
78+
function getZodChainableStringValidations(schema: OpenAPIV3.SchemaObject) {
7979
const validations: string[] = [];
8080

8181
if (!schema.enum) {
@@ -97,7 +97,7 @@ function getZodChainableStringValidations(schema: OpenAPIV3.SchemaObject, option
9797
.with("email", () => "email()")
9898
.with("hostname", "uri", () => "url()")
9999
.with("uuid", () => "uuid()")
100-
.with("date-time", () => `datetime({ offset: true })${options.branded ? ".brand('datetime')" : ""}`)
100+
.with("date-time", () => "datetime({ offset: true })")
101101
.otherwise(() => "");
102102

103103
if (chain) {

src/generators/core/zod/getZodSchema.ts

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { OpenAPIV3 } from "openapi-types";
22
import { BLOB_SCHEMA, ENUM_SCHEMA, STRING_SCHEMA } from "src/generators/const/zod.const";
33
import { GenerateType } from "src/generators/types/generate";
4+
import { getOtherBrands, getPrimitiveBrands, matchesBrand, wrapWithBrand } from "src/generators/utils/brand.utils";
45
import { getNamespaceName } from "src/generators/utils/generate/generate.utils";
56
import { match } from "ts-pattern";
67
import {
@@ -110,14 +111,22 @@ export function getZodSchema({ schema, resolver, meta: inheritedMeta, tag }: Get
110111
}
111112
}
112113

113-
const propZodSchema =
114+
let propZodSchema =
114115
getZodSchema({ ...params, schema: propSchema, meta: propMetadata }).getCodeString(tag, resolver.options) +
115116
getZodChain({
116117
schema: propActualSchema as OpenAPIV3.SchemaObject,
117118
meta: propMetadata,
118119
options: resolver.options,
119120
});
120121

122+
if (isSchemaObject(propSchema)) {
123+
getPrimitiveBrands().forEach((brand) => {
124+
if (matchesBrand(propSchema, brand)) {
125+
propZodSchema = wrapWithBrand(propZodSchema, brand, resolver.options);
126+
}
127+
});
128+
}
129+
121130
return [prop, propZodSchema];
122131
});
123132

@@ -143,7 +152,15 @@ export function getZodSchema({ schema, resolver, meta: inheritedMeta, tag }: Get
143152

144153
const partial = isPartial ? ".partial()" : "";
145154
const strict = resolver.options.strictObjects ? ".strict()" : "";
146-
return zodSchema.assign(`z.object(${properties})${partial}${strict}${additionalPropsSchema}${readonly}`);
155+
let zodObject = `z.object(${properties})${partial}${strict}${additionalPropsSchema}${readonly}`;
156+
157+
getOtherBrands().forEach((brand) => {
158+
if (matchesBrand(schema, brand)) {
159+
zodObject = wrapWithBrand(zodObject, brand, resolver.options);
160+
}
161+
});
162+
163+
return zodSchema.assign(zodObject);
147164
}
148165

149166
if ((schemaType as unknown) === "any") {

src/generators/generate/generateEndpoints.ts

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,11 @@
1-
import { APP_REST_CLIENT_NAME, ZOD_EXTENDED } from "../const/deps.const";
1+
import { APP_REST_CLIENT_NAME, ZOD_UTILS } from "../const/deps.const";
22
import { AXIOS_IMPORT, AXIOS_REQUEST_CONFIG_NAME, AXIOS_REQUEST_CONFIG_TYPE } from "../const/endpoints.const";
33
import { ZOD_IMPORT } from "../const/zod.const";
44
import { EndpointParameter } from "../types/endpoint";
55
import { GenerateType, GenerateTypeParams, Import } from "../types/generate";
66
import { getUniqueArray } from "../utils/array.utils";
77
import { getModelsImports } from "../utils/generate/generate.imports.utils";
8-
import {
9-
getAppRestClientImportPath,
10-
getNamespaceName,
11-
getZodExtendedImportPath,
12-
} from "../utils/generate/generate.utils";
8+
import { getAppRestClientImportPath, getNamespaceName, getZodUtilsImportPath } from "../utils/generate/generate.utils";
139
import { getHbsTemplateDelegate } from "../utils/hbs/hbs-template.utils";
1410
import { isNamedZodSchema } from "../utils/zod-schema.utils";
1511

@@ -42,10 +38,10 @@ export function generateEndpoints({ resolver, data, tag = "" }: GenerateTypePara
4238

4339
const hasZodImport = zodSchemas.some((schema) => !isNamedZodSchema(schema));
4440

45-
const hasZodExtendedImport = resolver.options.parseRequestParams && endpointParamsParseSchemas.length > 0;
46-
const zodExtendedImport: Import = {
47-
bindings: [ZOD_EXTENDED.name],
48-
from: getZodExtendedImportPath(resolver.options),
41+
const hasZodUtilsImport = resolver.options.parseRequestParams && endpointParamsParseSchemas.length > 0;
42+
const zodUtilsImport: Import = {
43+
bindings: [ZOD_UTILS.namespace],
44+
from: getZodUtilsImportPath(resolver.options),
4945
};
5046

5147
const modelsImports = getModelsImports({
@@ -63,8 +59,8 @@ export function generateEndpoints({ resolver, data, tag = "" }: GenerateTypePara
6359
axiosImport,
6460
hasZodImport,
6561
zodImport: ZOD_IMPORT,
66-
hasZodExtendedImport,
67-
zodExtendedImport,
62+
hasZodUtilsImport,
63+
zodUtilsImport,
6864
modelsImports,
6965
includeNamespace: resolver.options.tsNamespaces,
7066
namespace: getNamespaceName({ type: GenerateType.Endpoints, tag, options: resolver.options }),

src/generators/generate/generateModels.ts

Lines changed: 44 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
1+
import { BrandEnum } from "../const/brands.const";
2+
import { ZOD_UTILS } from "../const/deps.const";
13
import { ZOD_IMPORT } from "../const/zod.const";
4+
import { iterateSchema, OnSchemaCallbackData } from "../core/openapi/iterateSchema";
25
import { getZodSchemaRefs } from "../core/zod/getZodSchemaRefs";
3-
import { GenerateType, GenerateTypeParams, GenerateZodSchemaData } from "../types/generate";
6+
import { GenerateType, GenerateTypeParams, GenerateZodSchemaData, Import } from "../types/generate";
7+
import { matchesBrand } from "../utils/brand.utils";
48
import { getModelsImports } from "../utils/generate/generate.imports.utils";
5-
import { getNamespaceName } from "../utils/generate/generate.utils";
9+
import { getNamespaceName, getZodUtilsImportPath } from "../utils/generate/generate.utils";
610
import { getHbsTemplateDelegate } from "../utils/hbs/hbs-template.utils";
11+
import { isSchemaObject } from "../utils/openapi-schema.utils";
712
import { isEnumZodSchema } from "../utils/zod-schema.utils";
813

914
export function generateModels({ resolver, data, tag = "" }: GenerateTypeParams) {
@@ -25,23 +30,52 @@ export function generateModels({ resolver, data, tag = "" }: GenerateTypeParams)
2530
const zodSchemasData: Record<string, GenerateZodSchemaData> = Object.entries(zodSchemas).reduce(
2631
(acc, [key, code]) => {
2732
const ref = resolver.getRefByZodSchemaName(key);
28-
return {
29-
...acc,
30-
[key]: {
31-
code,
32-
isCircular: !!ref && resolver.isSchemaCircular(ref),
33-
isEnum: isEnumZodSchema(code),
34-
schemaObj: resolver.getZodSchemaObj(key),
35-
},
33+
const schemaObj = resolver.getZodSchemaObj(key);
34+
35+
let hasBrand = false;
36+
if (schemaObj && isSchemaObject(schemaObj)) {
37+
const onSchema = (data: OnSchemaCallbackData<never>) => {
38+
if (data.type === "reference") {
39+
return true;
40+
}
41+
Object.values(BrandEnum).forEach((brand) => {
42+
if (
43+
(data.schema && isSchemaObject(data.schema) && matchesBrand(data.schema, brand)) ||
44+
(data.parentSchema && isSchemaObject(data.parentSchema) && matchesBrand(data.parentSchema, brand))
45+
) {
46+
hasBrand = true;
47+
}
48+
});
49+
};
50+
iterateSchema(schemaObj, { onSchema });
51+
}
52+
53+
const value = {
54+
code,
55+
isCircular: !!ref && resolver.isSchemaCircular(ref),
56+
isEnum: isEnumZodSchema(code),
57+
schemaObj,
58+
hasBrand,
3659
};
60+
61+
return { ...acc, [key]: value };
3762
},
3863
{},
3964
);
4065

66+
const hasZodUtilsImport =
67+
resolver.options.branded && Object.values(zodSchemasData).some((zodSchema) => zodSchema.hasBrand);
68+
const zodUtilsImport: Import = {
69+
bindings: [ZOD_UTILS.namespace],
70+
from: getZodUtilsImportPath(resolver.options),
71+
};
72+
4173
const hbsTemplate = getHbsTemplateDelegate(resolver, "models");
4274

4375
return hbsTemplate({
4476
zodImport: ZOD_IMPORT,
77+
hasZodUtilsImport,
78+
zodUtilsImport,
4579
modelsImports,
4680
includeNamespace: resolver.options.tsNamespaces,
4781
namespace: getNamespaceName({ type: GenerateType.Models, tag, options: resolver.options }),

src/generators/generate/generateZod.ts renamed to src/generators/generate/generateZodUtils.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
1-
import { ERROR_HANDLERS, ERROR_HANDLING_IMPORT, ZOD_EXTENDED } from "../const/deps.const";
1+
import { ERROR_HANDLERS, ERROR_HANDLING_IMPORT, ZOD_UTILS } from "../const/deps.const";
22
import { ZOD_IMPORT } from "../const/zod.const";
33
import { SchemaResolver } from "../core/SchemaResolver.class";
44
import { getHbsTemplateDelegate } from "../utils/hbs/hbs-template.utils";
55

6-
export function generateZod(resolver: SchemaResolver) {
7-
const hbsTemplate = getHbsTemplateDelegate(resolver, "zod");
6+
export function generateZodUtils(resolver: SchemaResolver) {
7+
const hbsTemplate = getHbsTemplateDelegate(resolver, "zod-utils");
88

99
return hbsTemplate({
1010
zodImport: ZOD_IMPORT,
11-
zodExtension: ZOD_EXTENDED.name,
12-
sortingString: ZOD_EXTENDED.properties.sortingString,
13-
parse: ZOD_EXTENDED.properties.parse,
11+
zodUtilsNamespace: ZOD_UTILS.namespace,
12+
parse: ZOD_UTILS.exports.parse,
13+
sortExp: ZOD_UTILS.exports.sortExp,
14+
brand: ZOD_UTILS.exports.brand,
1415
errorHandler: ERROR_HANDLERS.ErrorHandler,
1516
sharedErrorHandler: ERROR_HANDLERS.SharedErrorHandler,
1617
errorHandlingImport: {

src/generators/generateCodeFromOpenAPIDoc.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import {
1212
getAclFiles,
1313
getMutationEffectsFiles,
1414
getStandaloneFiles,
15-
getZodExtendedFiles,
15+
getZodUtilsFiles,
1616
} from "./utils/generate-files.utils";
1717
import { getTagFileName } from "./utils/generate/generate.utils";
1818

@@ -51,7 +51,7 @@ export function generateCodeFromOpenAPIDoc(openApiDoc: OpenAPIV3.Document, cliOp
5151
generateFilesData.push(
5252
...getAclFiles(appAclTags, resolver),
5353
...getMutationEffectsFiles(data, resolver),
54-
...getZodExtendedFiles(data, resolver),
54+
...getZodUtilsFiles(data, resolver),
5555
...getStandaloneFiles(resolver),
5656
);
5757

src/generators/templates/endpoints.hbs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@
88
{{#if hasZodImport}}
99
{{{genImport zodImport}}}
1010
{{/if}}
11-
{{! Zod extended import }}
12-
{{#if hasZodExtendedImport}}
13-
{{{genImport zodExtendedImport}}}
11+
{{! Zod utils import }}
12+
{{#if hasZodUtilsImport}}
13+
{{{genImport zodUtilsImport}}}
1414
{{/if}}
1515
{{! Models import }}
1616
{{#each modelsImports as | modelsImport |}}

0 commit comments

Comments
 (0)