Skip to content

Commit 054d564

Browse files
authored
Fix schema objects with default + nullable (openapi-ts#1669)
1 parent a7dbe90 commit 054d564

File tree

9 files changed

+61
-33
lines changed

9 files changed

+61
-33
lines changed

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
"devDependencies": {
1818
"@biomejs/biome": "^1.7.3",
1919
"@changesets/changelog-github": "^0.5.0",
20-
"@changesets/cli": "^2.27.1",
20+
"@changesets/cli": "^2.27.2",
2121
"del-cli": "^5.1.0",
2222
"prettier": "^3.2.5",
2323
"typescript": "^5.4.5"

packages/openapi-typescript/examples/digital-ocean-api.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -9894,7 +9894,7 @@ export interface components {
98949894
* "web"
98959895
* ]
98969896
*/
9897-
tags: string[] | null;
9897+
tags: string[];
98989898
/**
98999899
* @description A string containing 'user data' which may be used to configure the Droplet on first boot, often a 'cloud-config' file or Bash script. It must be plain text and may not exceed 64 KiB in size.
99009900
* @example #cloud-config

packages/openapi-typescript/examples/github-api-export-type-immutable.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -29148,7 +29148,7 @@ export type components = {
2914829148
* @default RIGHT
2914929149
* @enum {string|null}
2915029150
*/
29151-
readonly start_side: "LEFT" | "RIGHT" | null;
29151+
readonly start_side: "LEFT" | "RIGHT";
2915229152
/**
2915329153
* @description The line of the blob to which the comment applies. The last line of the range for a multi-line comment
2915429154
* @example 2
@@ -30255,7 +30255,7 @@ export type components = {
3025530255
* @default RIGHT
3025630256
* @enum {string|null}
3025730257
*/
30258-
readonly start_side: "LEFT" | "RIGHT" | null;
30258+
readonly start_side: "LEFT" | "RIGHT";
3025930259
/**
3026030260
* @description The line of the blob to which the comment applies. The last line of the range for a multi-line comment
3026130261
* @example 2
@@ -101600,7 +101600,7 @@ export interface operations {
101600101600
/** @description The name of the task for the deployment (e.g., `deploy` or `deploy:migrations`). */
101601101601
readonly task?: string;
101602101602
/** @description The name of the environment that was deployed to (e.g., `staging` or `production`). */
101603-
readonly environment?: string | null;
101603+
readonly environment?: string;
101604101604
/** @description The number of results per page (max 100). For more information, see "[Using pagination in the REST API](https://docs.github.com/rest/using-the-rest-api/using-pagination-in-the-rest-api)." */
101605101605
readonly per_page?: components["parameters"]["per-page"];
101606101606
/** @description The page number of the results to fetch. For more information, see "[Using pagination in the REST API](https://docs.github.com/rest/using-the-rest-api/using-pagination-in-the-rest-api)." */

packages/openapi-typescript/examples/github-api-immutable.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -29148,7 +29148,7 @@ export interface components {
2914829148
* @default RIGHT
2914929149
* @enum {string|null}
2915029150
*/
29151-
readonly start_side: "LEFT" | "RIGHT" | null;
29151+
readonly start_side: "LEFT" | "RIGHT";
2915229152
/**
2915329153
* @description The line of the blob to which the comment applies. The last line of the range for a multi-line comment
2915429154
* @example 2
@@ -30255,7 +30255,7 @@ export interface components {
3025530255
* @default RIGHT
3025630256
* @enum {string|null}
3025730257
*/
30258-
readonly start_side: "LEFT" | "RIGHT" | null;
30258+
readonly start_side: "LEFT" | "RIGHT";
3025930259
/**
3026030260
* @description The line of the blob to which the comment applies. The last line of the range for a multi-line comment
3026130261
* @example 2
@@ -101600,7 +101600,7 @@ export interface operations {
101600101600
/** @description The name of the task for the deployment (e.g., `deploy` or `deploy:migrations`). */
101601101601
readonly task?: string;
101602101602
/** @description The name of the environment that was deployed to (e.g., `staging` or `production`). */
101603-
readonly environment?: string | null;
101603+
readonly environment?: string;
101604101604
/** @description The number of results per page (max 100). For more information, see "[Using pagination in the REST API](https://docs.github.com/rest/using-the-rest-api/using-pagination-in-the-rest-api)." */
101605101605
readonly per_page?: components["parameters"]["per-page"];
101606101606
/** @description The page number of the results to fetch. For more information, see "[Using pagination in the REST API](https://docs.github.com/rest/using-the-rest-api/using-pagination-in-the-rest-api)." */

packages/openapi-typescript/examples/github-api-next.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -101899,7 +101899,7 @@ export interface operations {
101899101899
/** @description The name of the task for the deployment (e.g., `deploy` or `deploy:migrations`). */
101900101900
task?: string;
101901101901
/** @description The name of the environment that was deployed to (e.g., `staging` or `production`). */
101902-
environment?: string | null;
101902+
environment?: string;
101903101903
/** @description The number of results per page (max 100). For more information, see "[Using pagination in the REST API](https://docs.github.com/rest/using-the-rest-api/using-pagination-in-the-rest-api)." */
101904101904
per_page?: components["parameters"]["per-page"];
101905101905
/** @description The page number of the results to fetch. For more information, see "[Using pagination in the REST API](https://docs.github.com/rest/using-the-rest-api/using-pagination-in-the-rest-api)." */

packages/openapi-typescript/examples/github-api.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -29148,7 +29148,7 @@ export interface components {
2914829148
* @default RIGHT
2914929149
* @enum {string|null}
2915029150
*/
29151-
start_side: "LEFT" | "RIGHT" | null;
29151+
start_side: "LEFT" | "RIGHT";
2915229152
/**
2915329153
* @description The line of the blob to which the comment applies. The last line of the range for a multi-line comment
2915429154
* @example 2
@@ -30255,7 +30255,7 @@ export interface components {
3025530255
* @default RIGHT
3025630256
* @enum {string|null}
3025730257
*/
30258-
start_side: "LEFT" | "RIGHT" | null;
30258+
start_side: "LEFT" | "RIGHT";
3025930259
/**
3026030260
* @description The line of the blob to which the comment applies. The last line of the range for a multi-line comment
3026130261
* @example 2
@@ -101600,7 +101600,7 @@ export interface operations {
101600101600
/** @description The name of the task for the deployment (e.g., `deploy` or `deploy:migrations`). */
101601101601
task?: string;
101602101602
/** @description The name of the environment that was deployed to (e.g., `staging` or `production`). */
101603-
environment?: string | null;
101603+
environment?: string;
101604101604
/** @description The number of results per page (max 100). For more information, see "[Using pagination in the REST API](https://docs.github.com/rest/using-the-rest-api/using-pagination-in-the-rest-api)." */
101605101605
per_page?: components["parameters"]["per-page"];
101606101606
/** @description The page number of the results to fetch. For more information, see "[Using pagination in the REST API](https://docs.github.com/rest/using-the-rest-api/using-pagination-in-the-rest-api)." */

packages/openapi-typescript/src/transform/schema-object.ts

+15-8
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,10 @@ export function transformSchemaObjectWithComposition(
116116
return ts.factory.createTypeReferenceNode(enumType.name);
117117
}
118118
const enumType = schemaObject.enum.map(tsLiteral);
119-
if ((Array.isArray(schemaObject.type) && schemaObject.type.includes("null")) || schemaObject.nullable) {
119+
if (
120+
((Array.isArray(schemaObject.type) && schemaObject.type.includes("null")) || schemaObject.nullable) &&
121+
!schemaObject.default
122+
) {
120123
enumType.push(NULL);
121124
}
122125

@@ -242,7 +245,7 @@ export function transformSchemaObjectWithComposition(
242245
// if final type could be generated, return intersection of all members
243246
if (finalType) {
244247
// deprecated nullable
245-
if (schemaObject.nullable) {
248+
if (schemaObject.nullable && !schemaObject.default) {
246249
return tsNullable([finalType]);
247250
}
248251
return finalType;
@@ -364,7 +367,7 @@ function transformSchemaObjectCore(schemaObject: SchemaObject, options: Transfor
364367
// polymorphic, or 3.1 nullable
365368
if (Array.isArray(schemaObject.type) && !Array.isArray(schemaObject)) {
366369
// skip any primitive types that appear in oneOf as well
367-
let uniqueTypes: ts.TypeNode[] = [];
370+
const uniqueTypes: ts.TypeNode[] = [];
368371
if (Array.isArray(schemaObject.oneOf)) {
369372
for (const t of schemaObject.type) {
370373
if (
@@ -383,11 +386,15 @@ function transformSchemaObjectCore(schemaObject: SchemaObject, options: Transfor
383386
);
384387
}
385388
} else {
386-
uniqueTypes = schemaObject.type.map((t) =>
387-
t === "null" || t === null
388-
? NULL
389-
: transformSchemaObject({ ...schemaObject, type: t } as SchemaObject, options),
390-
);
389+
for (const t of schemaObject.type) {
390+
if (t === "null" || t === null) {
391+
if (!schemaObject.default) {
392+
uniqueTypes.push(NULL);
393+
}
394+
} else {
395+
uniqueTypes.push(transformSchemaObject({ ...schemaObject, type: t } as SchemaObject, options));
396+
}
397+
}
391398
}
392399
return tsUnion(uniqueTypes);
393400
}

packages/openapi-typescript/test/transform/schema-object/string.test.ts

+21
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,27 @@ describe("transformSchemaObject > string", () => {
9191
want: '"A" | "B" | "C" | null',
9292
},
9393
],
94+
[
95+
"default + nullable",
96+
{
97+
given: { type: ["string", "null"], default: "en" },
98+
want: "string",
99+
},
100+
],
101+
[
102+
"default + nullable + enum",
103+
{
104+
given: { type: ["string", "null"], enum: ["en", "es", "fr", "de"], default: "en" },
105+
want: '"en" | "es" | "fr" | "de"',
106+
},
107+
],
108+
[
109+
"default + nullable (deprecated syntax)",
110+
{
111+
given: { type: "string", default: "en", nullable: true },
112+
want: "string",
113+
},
114+
],
94115
];
95116

96117
for (const [testName, { given, want, options = DEFAULT_OPTIONS, ci }] of tests) {

pnpm-lock.yaml

+13-13
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)