Skip to content

Commit 91b005f

Browse files
committed
fix: add parenthesis to handle priority between union/intersection
1 parent 4fb0b06 commit 91b005f

18 files changed

+139
-100
lines changed

.changeset/vast-places-live.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
"typed-openapi": minor
3+
---
4+
5+
add parenthesis to handle priority between union/intersection
6+
7+
this fixes an issue where `(A | B | C) & D` would be
8+
ambiguous and could be interpreted as `A | B | (C & D`

packages/typed-openapi/src/ts-factory.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ import { createFactory, unwrap } from "./box-factory.ts";
33
import { wrapWithQuotesIfNeeded } from "./string-utils.ts";
44

55
export const tsFactory = createFactory({
6-
union: (types) => types.map(unwrap).join(" | "),
7-
intersection: (types) => types.map(unwrap).join(" & "),
6+
union: (types) => `(${types.map(unwrap).join(" | ")})`,
7+
intersection: (types) => `(${types.map(unwrap).join(" & ")})`,
88
array: (type) => `Array<${unwrap(type)}>`,
99
optional: (type) => `${unwrap(type)} | undefined`,
1010
reference: (name, typeArgs) => `${name}${typeArgs ? `<${typeArgs.map(unwrap).join(", ")}>` : ""}`,

packages/typed-openapi/tests/generator-basic-schemas.test.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -266,10 +266,11 @@ test("getSchemaBox", async () => {
266266
]
267267
}),
268268
).toMatchInlineSnapshot(`
269-
"export type _Test =
269+
"export type _Test = (
270270
| { category: "finance"; chift?: { integrationId: number } | undefined }
271271
| { category: "hris"; kombo?: { integrationId: string } | undefined }
272-
| ({ category: "it-and-security" } & { sourceName: string });"
272+
| { category: "it-and-security" }
273+
) & { sourceName: string };"
273274
`);
274275

275276
expect(await getSchemaBox({ type: "string", enum: ["aaa", "bbb", "ccc"] })).toMatchInlineSnapshot(
@@ -485,7 +486,7 @@ describe("getSchemaBox with context", () => {
485486
`
486487
{
487488
"type": "ref",
488-
"value": "Partial<{ user: User | Member, users: Array<User | Member>, basic: number }>",
489+
"value": "Partial<{ user: (User | Member), users: Array<(User | Member)>, basic: number }>",
489490
}
490491
`,
491492
);

packages/typed-openapi/tests/generator.test.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ describe("generator", () => {
3939
category?: Category | undefined;
4040
photoUrls: Array<string>;
4141
tags?: Array<Tag> | undefined;
42-
status?: "available" | "pending" | "sold" | undefined;
42+
status?: ("available" | "pending" | "sold") | undefined;
4343
};
4444
export type ApiResponse = Partial<{ code: number; type: string; message: string }>;
4545
@@ -669,9 +669,9 @@ describe("generator", () => {
669669
accessTokenExpirationDate: number;
670670
me: {
671671
id: string;
672-
firstName?: string | null | undefined;
673-
lastName?: string | null | undefined;
674-
profilePictureURL?: string | null | undefined;
672+
firstName?: (string | null) | undefined;
673+
lastName?: (string | null) | undefined;
674+
profilePictureURL?: (string | null) | undefined;
675675
email: string;
676676
};
677677
refreshToken: string;
@@ -698,10 +698,10 @@ describe("generator", () => {
698698
response: {
699699
members: Array<{
700700
id: string;
701-
firstName?: string | null | undefined;
702-
lastName?: string | null | undefined;
701+
firstName?: (string | null) | undefined;
702+
lastName?: (string | null) | undefined;
703703
email: string;
704-
profilePictureURL?: string | null | undefined;
704+
profilePictureURL?: (string | null) | undefined;
705705
}>;
706706
};
707707
};

packages/typed-openapi/tests/map-openapi-endpoints.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1473,7 +1473,7 @@ describe("map-openapi-endpoints", () => {
14731473
"parameters": {
14741474
"query": {
14751475
"type": "ref",
1476-
"value": "Partial<{ status: "available" | "pending" | "sold" }>",
1476+
"value": "Partial<{ status: ("available" | "pending" | "sold") }>",
14771477
},
14781478
},
14791479
"path": "/pet/findByStatus",

packages/typed-openapi/tests/openapi-schema-to-ts.test.ts

Lines changed: 32 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ test("getSchemaBox", () => {
3131
expect(getSchemaBox({ type: "boolean", nullable: true })).toMatchInlineSnapshot(`
3232
{
3333
"type": "union",
34-
"value": "boolean | null",
34+
"value": "(boolean | null)",
3535
}
3636
`);
3737
expect(getSchemaBox({ type: "string" })).toMatchInlineSnapshot(`
@@ -61,7 +61,7 @@ test("getSchemaBox", () => {
6161
expect(getSchemaBox({ type: "string", nullable: true })).toMatchInlineSnapshot(`
6262
{
6363
"type": "union",
64-
"value": "string | null",
64+
"value": "(string | null)",
6565
}
6666
`);
6767

@@ -75,7 +75,7 @@ test("getSchemaBox", () => {
7575
expect(getSchemaBox({ type: "array", items: { type: "string", nullable: true } })).toMatchInlineSnapshot(`
7676
{
7777
"type": "array",
78-
"value": "Array<string | null>",
78+
"value": "Array<(string | null)>",
7979
}
8080
`);
8181
expect(getSchemaBox({ type: "object" })).toMatchInlineSnapshot(
@@ -103,7 +103,7 @@ test("getSchemaBox", () => {
103103
.toMatchInlineSnapshot(`
104104
{
105105
"type": "ref",
106-
"value": "Partial<{ str: string, nb: number, nullable: string | null }>",
106+
"value": "Partial<{ str: string, nb: number, nullable: (string | null) }>",
107107
}
108108
`);
109109

@@ -163,7 +163,7 @@ test("getSchemaBox", () => {
163163
).toMatchInlineSnapshot(`
164164
{
165165
"type": "ref",
166-
"value": "Partial<{ str: string } & { string: number }>",
166+
"value": "Partial<({ str: string } & { string: number })>",
167167
}
168168
`);
169169

@@ -177,7 +177,7 @@ test("getSchemaBox", () => {
177177
).toMatchInlineSnapshot(`
178178
{
179179
"type": "ref",
180-
"value": "Partial<{ str: string } & { string: Partial<{ prop: boolean }> }>",
180+
"value": "Partial<({ str: string } & { string: Partial<{ prop: boolean }> })>",
181181
}
182182
`);
183183

@@ -195,7 +195,7 @@ test("getSchemaBox", () => {
195195
).toMatchInlineSnapshot(`
196196
{
197197
"type": "array",
198-
"value": "Array<Partial<{ str: string, nullable: string | null }>>",
198+
"value": "Array<Partial<{ str: string, nullable: (string | null) }>>",
199199
}
200200
`);
201201

@@ -227,15 +227,15 @@ test("getSchemaBox", () => {
227227
).toMatchInlineSnapshot(`
228228
{
229229
"type": "ref",
230-
"value": "Partial<{ enumprop: "aaa" | "bbb" | "ccc" }>",
230+
"value": "Partial<{ enumprop: ("aaa" | "bbb" | "ccc") }>",
231231
}
232232
`);
233233

234234
expect(getSchemaBox({ type: "string", enum: ["aaa", "bbb", "ccc"] })).toMatchInlineSnapshot(
235235
`
236236
{
237237
"type": "union",
238-
"value": ""aaa" | "bbb" | "ccc"",
238+
"value": "("aaa" | "bbb" | "ccc")",
239239
}
240240
`,
241241
);
@@ -244,7 +244,7 @@ test("getSchemaBox", () => {
244244
expect(getSchemaBox({ type: "string", enum: ["aaa", "bbb", "ccc"] })).toMatchInlineSnapshot(`
245245
{
246246
"type": "union",
247-
"value": ""aaa" | "bbb" | "ccc"",
247+
"value": "("aaa" | "bbb" | "ccc")",
248248
}
249249
`);
250250

@@ -259,48 +259,48 @@ test("getSchemaBox", () => {
259259
).toMatchInlineSnapshot(`
260260
{
261261
"type": "ref",
262-
"value": "Partial<{ union: string | number }>",
262+
"value": "Partial<{ union: (string | number) }>",
263263
}
264264
`);
265265
expect(getSchemaBox({ oneOf: [{ type: "string" }, { type: "number" }] })).toMatchInlineSnapshot(
266266
`
267267
{
268268
"type": "union",
269-
"value": "string | number",
269+
"value": "(string | number)",
270270
}
271271
`,
272272
);
273273

274274
// StringOrNumber
275275
expect(getSchemaBox({ oneOf: [{ type: "string" }, { type: "number" }] })).toMatchInlineSnapshot(`
276-
{
277-
"type": "union",
278-
"value": "string | number",
279-
}
280-
`);
276+
{
277+
"type": "union",
278+
"value": "(string | number)",
279+
}
280+
`);
281281

282282
expect(getSchemaBox({ allOf: [{ type: "string" }, { type: "number" }] })).toMatchInlineSnapshot(
283283
`
284284
{
285285
"type": "intersection",
286-
"value": "string & number",
286+
"value": "(string & number)",
287287
}
288288
`,
289289
);
290290

291291
// StringAndNumber
292292
expect(getSchemaBox({ allOf: [{ type: "string" }, { type: "number" }] })).toMatchInlineSnapshot(`
293-
{
294-
"type": "intersection",
295-
"value": "string & number",
296-
}
297-
`);
293+
{
294+
"type": "intersection",
295+
"value": "(string & number)",
296+
}
297+
`);
298298

299299
expect(getSchemaBox({ anyOf: [{ type: "string" }, { type: "number" }] })).toMatchInlineSnapshot(
300300
`
301301
{
302302
"type": "union",
303-
"value": "string | number",
303+
"value": "(string | number)",
304304
}
305305
`,
306306
);
@@ -309,7 +309,7 @@ test("getSchemaBox", () => {
309309
expect(getSchemaBox({ anyOf: [{ type: "string" }, { type: "number" }] })).toMatchInlineSnapshot(`
310310
{
311311
"type": "union",
312-
"value": "string | number",
312+
"value": "(string | number)",
313313
}
314314
`);
315315

@@ -324,7 +324,7 @@ test("getSchemaBox", () => {
324324
).toMatchInlineSnapshot(`
325325
{
326326
"type": "ref",
327-
"value": "Partial<{ unionOrArrayOfUnion: string | number }>",
327+
"value": "Partial<{ unionOrArrayOfUnion: (string | number) }>",
328328
}
329329
`);
330330

@@ -339,22 +339,22 @@ test("getSchemaBox", () => {
339339
).toMatchInlineSnapshot(`
340340
{
341341
"type": "ref",
342-
"value": "Partial<{ intersection: string & number }>",
342+
"value": "Partial<{ intersection: (string & number) }>",
343343
}
344344
`);
345345

346346
expect(getSchemaBox({ type: "string", enum: ["aaa", "bbb", "ccc"] })).toMatchInlineSnapshot(
347347
`
348348
{
349349
"type": "union",
350-
"value": ""aaa" | "bbb" | "ccc"",
350+
"value": "("aaa" | "bbb" | "ccc")",
351351
}
352352
`,
353353
);
354354
expect(getSchemaBox({ type: "number", enum: [1, 2, 3] })).toMatchInlineSnapshot(`
355355
{
356356
"type": "union",
357-
"value": "1 | 2 | 3",
357+
"value": "(1 | 2 | 3)",
358358
}
359359
`);
360360

@@ -398,7 +398,7 @@ test("getSchemaBox", () => {
398398
})).toMatchInlineSnapshot(`
399399
{
400400
"type": "object",
401-
"value": "{ members: Array<{ id: string, firstName?: string | null | undefined, lastName?: string | null | undefined, email: string, profilePictureURL?: string | null | undefined }> }",
401+
"value": "{ members: Array<{ id: string, firstName?: (string | null) | undefined, lastName?: (string | null) | undefined, email: string, profilePictureURL?: (string | null) | undefined }> }",
402402
}
403403
`);
404404
});
@@ -610,7 +610,7 @@ describe("getSchemaBox with context", () => {
610610
`
611611
{
612612
"type": "ref",
613-
"value": "Partial<{ user: User | Member, users: Array<User | Member>, basic: number }>",
613+
"value": "Partial<{ user: (User | Member), users: Array<(User | Member)>, basic: number }>",
614614
}
615615
`,
616616
);
@@ -634,7 +634,7 @@ describe("getSchemaBox with context", () => {
634634
`
635635
{
636636
"type": "ref",
637-
"value": "Partial<{ name: string | null }>",
637+
"value": "Partial<{ name: (string | null) }>",
638638
}
639639
`,
640640
);

packages/typed-openapi/tests/snapshots/docker.openapi.client.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -306,7 +306,7 @@ export namespace Schemas {
306306
Scope: "local" | "global";
307307
ClusterVolume?: ClusterVolume | undefined;
308308
Options: Record<string, string>;
309-
UsageData?: { Size: number; RefCount: number } | null | undefined;
309+
UsageData?: ({ Size: number; RefCount: number } | null) | undefined;
310310
};
311311
export type VolumeCreateOptions = Partial<{
312312
Name: string;
@@ -413,7 +413,7 @@ export namespace Schemas {
413413
Interface: {
414414
Types: Array<PluginInterfaceType>;
415415
Socket: string;
416-
ProtocolScheme?: "" | "moby.plugins.http/v1" | undefined;
416+
ProtocolScheme?: ("" | "moby.plugins.http/v1") | undefined;
417417
};
418418
Entrypoint: Array<string>;
419419
WorkDir: string;
@@ -726,7 +726,7 @@ export namespace Schemas {
726726
export type ContainerWaitResponse = { StatusCode: number; Error?: ContainerWaitExitError | undefined };
727727
export type SystemVersion = Partial<{
728728
Platform: { Name: string };
729-
Components: Array<{ Name: string; Version: string; Details?: Partial<{}> | null | undefined }>;
729+
Components: Array<{ Name: string; Version: string; Details?: (Partial<{}> | null) | undefined }>;
730730
Version: string;
731731
ApiVersion: string;
732732
MinAPIVersion: string;
@@ -1896,7 +1896,11 @@ export namespace Endpoints {
18961896
path: "/services/{id}/update";
18971897
requestFormat: "json";
18981898
parameters: {
1899-
query: { version: number; registryAuthFrom: "spec" | "previous-spec" | undefined; rollback: string | undefined };
1899+
query: {
1900+
version: number;
1901+
registryAuthFrom: ("spec" | "previous-spec") | undefined;
1902+
rollback: string | undefined;
1903+
};
19001904
path: { id: string };
19011905
header: Partial<{ "X-Registry-Auth": string }>;
19021906
body: Schemas.ServiceSpec & Record<string, unknown>;

0 commit comments

Comments
 (0)