Skip to content

Commit 2a84534

Browse files
feat(flags): add flag to force never response body (#905)
The OpenAPI specs state that in order to describe an Empty Response Body, the response `content` section should be omitted. https://swagger.io/docs/specification/describing-responses/ The current behavior generates the expected `never` type for a handful of status code which should always return an empty response body. However, when an app returns an empty response body for a status code outside of this short list, we lose type safety. This flag allows developers to generate types with the `never` type used for every response with no `content`
1 parent 5cce3c7 commit 2a84534

File tree

8 files changed

+73
-3
lines changed

8 files changed

+73
-3
lines changed

bin/cli.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ Options
2121
--header, -x (optional) Provide an array of or singular headers as an alternative to a JSON object. Each header must follow the key: value pattern
2222
--httpMethod, -m (optional) Provide the HTTP Verb/Method for fetching a schema from a remote URL
2323
--immutable-types, -it (optional) Generates immutable types (readonly properties and readonly array)
24+
--content-never (optional) If supplied, an omitted reponse \`content\` property will be generated as \`never\` instead of \`unknown\`
2425
--additional-properties, -ap (optional) Allow arbitrary properties for all schema objects without "additionalProperties: false"
2526
--default-non-nullable (optional) If a schema object has a default value set, don’t mark it as nullable
2627
--prettier-config, -c (optional) specify path to Prettier config file
@@ -45,7 +46,7 @@ function errorAndExit(errorMessage) {
4546
const [, , input, ...args] = process.argv;
4647
const flags = parser(args, {
4748
array: ["header"],
48-
boolean: ["defaultNonNullable", "immutableTypes", "rawSchema", "exportType", "supportArrayLength", "makePathsEnum", "pathParamsAsTypes"],
49+
boolean: ["defaultNonNullable", "immutableTypes", "contentNever", "rawSchema", "exportType", "supportArrayLength", "makePathsEnum", "pathParamsAsTypes"],
4950
number: ["version"],
5051
string: ["auth", "header", "headersObject", "httpMethod", "prettierConfig"],
5152
alias: {
@@ -92,6 +93,7 @@ async function generateSchema(pathToSpec) {
9293
prettierConfig: flags.prettierConfig,
9394
rawSchema: flags.rawSchema,
9495
makePathsEnum: flags.makePathsEnum,
96+
contentNever: flags.contentNever,
9597
silent: output === OUTPUT_STDOUT,
9698
version: flags.version,
9799
httpHeaders,

src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ async function openapiTS(
4343
defaultNonNullable: options.defaultNonNullable || false,
4444
formatter: options && typeof options.formatter === "function" ? options.formatter : undefined,
4545
immutableTypes: options.immutableTypes || false,
46+
contentNever: options.contentNever || false,
4647
makePathsEnum: options.makePathsEnum || false,
4748
pathParamsAsTypes: options.pathParamsAsTypes,
4849
rawSchema: options.rawSchema || false,

src/transform/responses.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,15 @@ import { comment, tsReadonly } from "../utils.js";
33
import { transformHeaderObjMap } from "./headers.js";
44
import { transformSchemaObj } from "./schema.js";
55

6-
const resType = (res: string | number) => (res === 204 || (res >= 300 && res < 400) ? "never" : "unknown");
7-
86
export function transformResponsesObj(responsesObj: Record<string, any>, ctx: GlobalContext): string {
97
const readonly = tsReadonly(ctx.immutableTypes);
8+
const resType = (res: string | number) => {
9+
if (tsReadonly(ctx.contentNever)) {
10+
return "never";
11+
} else {
12+
return res === 204 || (res >= 300 && res < 400) ? "never" : "unknown";
13+
}
14+
};
1015

1116
let output = "";
1217

src/types.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,8 @@ export interface SwaggerToTSOptions {
126126
formatter?: SchemaFormatter;
127127
/** Generates immutable types (readonly properties and readonly array) */
128128
immutableTypes?: boolean;
129+
/** (optional) If supplied, an omitted reponse \`content\` property will be generated as \`never\` instead of \`unknown\` */
130+
contentNever?: boolean;
129131
/** (optional) Treat schema objects with default values as non-nullable */
130132
defaultNonNullable?: boolean;
131133
/** (optional) Path to Prettier config */
@@ -180,6 +182,7 @@ export interface GlobalContext {
180182
defaultNonNullable: boolean;
181183
formatter?: SchemaFormatter;
182184
immutableTypes: boolean;
185+
contentNever: boolean;
183186
makePathsEnum: boolean;
184187
namespace?: string;
185188
pathParamsAsTypes?: boolean;

test/bin/cli.test.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,4 +89,14 @@ describe("cli", () => {
8989
const expected = eol.lf(fs.readFileSync(new URL("./expected/paths-enum.ts", cwd), "utf8"));
9090
expect(generated).to.equal(expected);
9191
});
92+
93+
it('generates the `never` type for omitted response `content` with --content-never', () => {
94+
const generatedPath = "generated/content-never.ts"
95+
execSync(`${cmd} specs/no-response.yaml -o ${generatedPath} --content-never`, {
96+
cwd,
97+
});
98+
const generated = fs.readFileSync(new URL(`./${generatedPath}`, cwd), "utf8");
99+
const expected = eol.lf(fs.readFileSync(new URL("./expected/content-never.ts", cwd), "utf8"));
100+
expect(generated).to.equal(expected);
101+
});
92102
});

test/bin/expected/content-never.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/**
2+
* This file was auto-generated by openapi-typescript.
3+
* Do not make direct changes to the file.
4+
*/
5+
6+
export interface paths {
7+
"/test": {
8+
get: {
9+
responses: {
10+
/** OK */
11+
200: never;
12+
};
13+
};
14+
};
15+
}
16+
17+
export interface components {}
18+
19+
export interface operations {}
20+
21+
export interface external {}

test/bin/expected/no-response.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/**
2+
* This file was auto-generated by openapi-typescript.
3+
* Do not make direct changes to the file.
4+
*/
5+
6+
export interface paths {
7+
"/test": {
8+
get: {
9+
responses: {
10+
/** OK */
11+
200: never;
12+
};
13+
};
14+
};
15+
}
16+
17+
export interface components {}
18+
19+
export interface operations {}
20+
21+
export interface external {}

test/bin/specs/no-response.yaml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
openapi: 3.0
2+
paths:
3+
/test:
4+
get:
5+
responses:
6+
200:
7+
description: OK

0 commit comments

Comments
 (0)