Skip to content

Commit 5a21e68

Browse files
committed
Add branded zod schemas
1 parent beca0e9 commit 5a21e68

10 files changed

Lines changed: 44 additions & 19 deletions

File tree

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,12 +45,15 @@ yarn openapi-codegen generate --input http://localhost:3001/docs-json --standalo
4545
--importPath Module import style for generated files (default: 'ts'; options: 'ts' | 'relative' | 'absolute')
4646
--removeOperationPrefixEndingWith Remove operation name prefixes that end with the specified string (Default: 'Controller_')
4747
--extractEnums Extract enums into separate Zod schemas (default: true)
48+
--branded Apply branded types to ambiguous Zod schemas (default: true)
4849
--replaceOptionalWithNullish Replace `.optional()` chains with `.nullish()` in generated Zod schemas (default: false)
4950
5051
--axiosRequestConfig Include Axios request config parameters in query hooks (default: false)
5152
--infiniteQueries Generate infinite queries for paginated API endpoints (default: false)
5253
--mutationEffects Add mutation effects options to mutation hooks (default: true)
5354
--parseRequestParams Add Zod parsing to API endpoints (default: false)
55+
56+
--acl Generate ACL related files (default: true)
5457
--checkAcl Add ACL check to queries (default: false)
5558
5659
--standalone Generate any missing supporting classes/types, e.g., REST client class, React Query type extensions, etc. (default: false)

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.2",
3+
"version": "0.13.3",
44
"main": "./dist/index.js",
55
"bin": {
66
"openapi-codegen": "./dist/sh.js"

src/commands/generate.command.ts

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -46,12 +46,33 @@ class GenerateOptions implements GenerateParams {
4646
})
4747
removeOperationPrefixEndingWith!: string;
4848

49+
@YargOption({
50+
envAlias: "acl",
51+
default: DEFAULT_GENERATE_OPTIONS.acl,
52+
type: "boolean",
53+
})
54+
acl!: boolean;
55+
56+
@YargOption({
57+
envAlias: "checkAcl",
58+
default: DEFAULT_GENERATE_OPTIONS.checkAcl,
59+
type: "boolean",
60+
})
61+
checkAcl!: boolean;
62+
4963
@YargOption({ envAlias: "standalone", default: DEFAULT_GENERATE_OPTIONS.standalone, type: "boolean" })
5064
standalone!: boolean;
5165

5266
@YargOption({ envAlias: "baseUrl", default: DEFAULT_GENERATE_OPTIONS.baseUrl })
5367
baseUrl!: string;
5468

69+
@YargOption({
70+
envAlias: "branded",
71+
default: DEFAULT_GENERATE_OPTIONS.branded,
72+
type: "boolean",
73+
})
74+
branded!: boolean;
75+
5576
@YargOption({
5677
envAlias: "replaceOptionalWithNullish",
5778
default: DEFAULT_GENERATE_OPTIONS.replaceOptionalWithNullish,
@@ -76,13 +97,6 @@ class GenerateOptions implements GenerateParams {
7697
})
7798
parseRequestParams!: boolean;
7899

79-
@YargOption({
80-
envAlias: "checkAcl",
81-
default: DEFAULT_GENERATE_OPTIONS.checkAcl,
82-
type: "boolean",
83-
})
84-
checkAcl!: boolean;
85-
86100
@YargOption({ envAlias: "axiosRequestConfig", default: DEFAULT_GENERATE_OPTIONS.axiosRequestConfig, type: "boolean" })
87101
axiosRequestConfig!: boolean;
88102

src/commands/generate.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,16 @@ export type GenerateParams = {
2222
| "removeOperationPrefixEndingWith"
2323
| "importPath"
2424
| "extractEnums"
25+
| "acl"
26+
| "checkAcl"
2527
| "standalone"
2628
| "baseUrl"
29+
| "branded"
2730
| "replaceOptionalWithNullish"
2831
| "infiniteQueries"
2932
| "axiosRequestConfig"
3033
| "mutationEffects"
3134
| "parseRequestParams"
32-
| "checkAcl"
3335
>;
3436

3537
export async function generate({ input, excludeTags, monorepo, prettier, verbose, ...params }: GenerateParams) {

src/generators/const/options.const.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ export const DEFAULT_GENERATE_OPTIONS: GenerateOptions = {
3232
namespaceSuffix: "Acl",
3333
},
3434
},
35+
acl: true,
36+
checkAcl: false,
3537
standalone: false,
3638
baseUrl: "",
3739
abilityContextImportPath: TEMPLATE_IMPORTS.abilityContext.template,
@@ -40,13 +42,13 @@ export const DEFAULT_GENERATE_OPTIONS: GenerateOptions = {
4042
enumSuffix: ENUM_SUFFIX,
4143
withDefaultValues: true,
4244
extractEnums: true,
45+
branded: true,
4346
replaceOptionalWithNullish: false,
4447
// Endpoints options
4548
restClientImportPath: TEMPLATE_IMPORTS.appRestClient.template,
4649
errorHandlingImportPath: TEMPLATE_IMPORTS.errorHandling.template,
4750
removeOperationPrefixEndingWith: "Controller_",
4851
parseRequestParams: false,
49-
checkAcl: false,
5052
// Queries options
5153
queryTypesImportPath: TEMPLATE_IMPORTS.queryTypes.template,
5254
axiosRequestConfig: false,

src/generators/core/endpoints/getEndpointsFromOpenAPIDoc.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ describe("getEndpointsFromOpenAPIDoc", () => {
168168
},
169169
]);
170170
expect(resolver.getZodSchemas()).toStrictEqual({
171-
Order: `z.object({ id: z.number().int(), petId: z.number().int(), quantity: z.number().int(), shipDate: z.string().datetime({ offset: true }), status: z.enum(["placed", "approved", "delivered"]), complete: z.boolean() }).partial()`,
171+
Order: `z.object({ id: z.number().int(), petId: z.number().int(), quantity: z.number().int(), shipDate: z.string().datetime({ offset: true }).brand('datetime'), status: z.enum(["placed", "approved", "delivered"]), complete: z.boolean() }).partial()`,
172172
});
173173
expect(resolver["compositeZodSchemaData"]).toStrictEqual([]);
174174
});
@@ -1691,7 +1691,7 @@ describe("getEndpointsFromOpenAPIDoc", () => {
16911691
FindPetsByTagsResponse: "z.array(Pet)",
16921692
FindPetsByTagsTagsParam: "z.array(z.string()).optional()",
16931693
GetInventoryResponse: "z.object({}).catchall(z.number().int())",
1694-
Order: `z.object({ id: z.number().int(), petId: z.number().int(), quantity: z.number().int(), shipDate: z.string().datetime({ offset: true }), status: z.enum(["placed", "approved", "delivered"]), complete: z.boolean() }).partial()`,
1694+
Order: `z.object({ id: z.number().int(), petId: z.number().int(), quantity: z.number().int(), shipDate: z.string().datetime({ offset: true }).brand('datetime'), status: z.enum(["placed", "approved", "delivered"]), complete: z.boolean() }).partial()`,
16951695
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() })`,
16961696
Tag: "z.object({ id: z.number().int(), name: z.string() }).partial()",
16971697
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()",

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)))
19+
.with("string", () => chains.push(getZodChainableStringValidations(schema, options)))
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) {
78+
function getZodChainableStringValidations(schema: OpenAPIV3.SchemaObject, options: GenerateOptions) {
7979
const validations: string[] = [];
8080

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

103103
if (chain) {

src/generators/core/zod/getZodSchema.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ describe("getZodSchema", () => {
5858

5959
expect(
6060
getZodSchemaString({ type: "object", properties: { dt: { type: "string", format: "date-time" } } }),
61-
).toStrictEqual("z.object({ dt: z.string().datetime({ offset: true }) }).partial()");
61+
).toStrictEqual("z.object({ dt: z.string().datetime({ offset: true }).brand('datetime') }).partial()");
6262

6363
expect(
6464
getZodSchemaString({

src/generators/types/options.d.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ interface ZodGenerateOptions {
99
allReadonly?: boolean;
1010
strictObjects?: boolean;
1111
extractEnums?: boolean;
12+
branded?: boolean;
1213
replaceOptionalWithNullish?: boolean;
1314
}
1415

@@ -18,14 +19,14 @@ interface EndpointsGenerateOptions {
1819
withDeprecatedEndpoints?: boolean;
1920
removeOperationPrefixEndingWith?: string;
2021
parseRequestParams?: boolean;
21-
checkAcl?: boolean;
2222
}
2323

2424
interface QueriesGenerateOptions {
2525
queryTypesImportPath: string;
2626
axiosRequestConfig?: boolean;
2727
infiniteQueries?: boolean;
2828
mutationEffects?: boolean;
29+
checkAcl?: boolean;
2930
}
3031

3132
interface GenerateConfig {
@@ -44,6 +45,7 @@ interface BaseGenerateOptions {
4445
tsNamespaces: boolean;
4546
importPath: "ts" | "relative" | "absolute";
4647
configs: Record<GenerateType, GenerateConfig>;
48+
acl: boolean;
4749
standalone: boolean;
4850
baseUrl: string;
4951
abilityContextImportPath: string;

src/generators/utils/generate-files.utils.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,17 @@ import { getOutputFileName, readAssetSync } from "./file.utils";
1818
import { getFileNameWithExtension } from "./generate/generate.utils";
1919

2020
export function getAclFiles(appAclTags: string[], resolver: SchemaResolver): GenerateFileData[] {
21-
const appAclContent = generateAppAcl(resolver, appAclTags);
21+
if (!resolver.options.acl) {
22+
return [];
23+
}
2224

2325
return [
2426
{
2527
fileName: getOutputFileName({
2628
output: resolver.options.output,
2729
fileName: getFileNameWithExtension(ACL_APP_ABILITY_FILE),
2830
}),
29-
content: appAclContent,
31+
content: generateAppAcl(resolver, appAclTags),
3032
},
3133
...(resolver.options.checkAcl
3234
? [

0 commit comments

Comments
 (0)