Skip to content

Commit d32d84a

Browse files
authored
feat!: Improve error type safety (#63)
* Add getErrorResponseType * Extract getDataResponseType * Use new response types * Add test to getUsedImports * Extract testUtils * Fix template.test.ts type issues * Add createNamespaceImport helper * Generate new error format * Generate utils * Inject TError * Export allError type * Generate utils file * Tweak fetcher template * Remove all errors export * Improve fallback error * Generate utils only when needed * Ensure error format in every situation
1 parent 8d0d287 commit d32d84a

20 files changed

+1292
-140
lines changed
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { factory as f } from "typescript";
2+
3+
/**
4+
* Helper to create namespace import.
5+
*
6+
* @param namespace namespace import identifier
7+
* @param filename path of the module
8+
* @returns ts.Node of the import declaration
9+
*/
10+
export const createNamespaceImport = (namespace: string, filename: string) =>
11+
f.createImportDeclaration(
12+
undefined,
13+
undefined,
14+
f.createImportClause(
15+
true,
16+
undefined,
17+
f.createNamespaceImport(f.createIdentifier(namespace))
18+
),
19+
f.createStringLiteral(filename),
20+
undefined
21+
);

plugins/typescript/src/core/createOperationFetcherFnNodes.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import ts, { factory as f } from "typescript";
99
*/
1010
export const createOperationFetcherFnNodes = ({
1111
dataType,
12+
errorType,
1213
requestBodyType,
1314
queryParamsType,
1415
pathParamsType,
@@ -21,6 +22,7 @@ export const createOperationFetcherFnNodes = ({
2122
name,
2223
}: {
2324
dataType: ts.TypeNode;
25+
errorType: ts.TypeNode;
2426
requestBodyType: ts.TypeNode;
2527
headersType: ts.TypeNode;
2628
pathParamsType: ts.TypeNode;
@@ -68,6 +70,7 @@ export const createOperationFetcherFnNodes = ({
6870
f.createIdentifier(fetcherFn),
6971
[
7072
dataType,
73+
errorType,
7174
requestBodyType,
7275
headersType,
7376
queryParamsType,
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
import { ResponsesObject } from "openapi3-ts";
2+
import { print } from "../testUtils";
3+
import { getDataResponseType } from "./getDataResponseType";
4+
5+
describe("getDataResponseType", () => {
6+
it("should extract the response type with 200 status", () => {
7+
const responses: ResponsesObject = {
8+
"200": {
9+
description: "pet response",
10+
content: {
11+
"application/json": {
12+
schema: {
13+
type: "array",
14+
items: {
15+
$ref: "#/components/schemas/Pet",
16+
},
17+
},
18+
},
19+
},
20+
},
21+
default: {
22+
description: "unexpected error",
23+
content: {
24+
"application/json": {
25+
schema: {
26+
$ref: "#/components/schemas/Error",
27+
},
28+
},
29+
},
30+
},
31+
};
32+
33+
const responseType = getDataResponseType({
34+
responses,
35+
printNodes: (nodes) => nodes.map(print).join("\n"),
36+
});
37+
38+
expect(print(responseType)).toMatchInlineSnapshot(`"Schemas.Pet[]"`);
39+
});
40+
41+
it("should remove duplicate responses", () => {
42+
const responses: ResponsesObject = {
43+
"200": {
44+
description: "pet response",
45+
content: {
46+
"application/json": {
47+
schema: {
48+
type: "array",
49+
items: {
50+
$ref: "#/components/schemas/Pet",
51+
},
52+
},
53+
},
54+
},
55+
},
56+
"203": {
57+
description: "pet response",
58+
content: {
59+
"application/json": {
60+
schema: {
61+
type: "array",
62+
items: {
63+
$ref: "#/components/schemas/Pet",
64+
},
65+
},
66+
},
67+
},
68+
},
69+
};
70+
71+
const responseType = getDataResponseType({
72+
responses,
73+
printNodes: (nodes) => nodes.map(print).join("\n"),
74+
});
75+
76+
expect(print(responseType)).toMatchInlineSnapshot(`"Schemas.Pet[]"`);
77+
});
78+
79+
it("should create a union", () => {
80+
const responses: ResponsesObject = {
81+
"200": {
82+
description: "pet response",
83+
content: {
84+
"application/json": {
85+
schema: {
86+
type: "array",
87+
items: {
88+
$ref: "#/components/schemas/Pet",
89+
},
90+
},
91+
},
92+
},
93+
},
94+
"203": {
95+
description: "pet response",
96+
content: {
97+
"application/json": {
98+
schema: {
99+
type: "array",
100+
items: {
101+
$ref: "#/components/schemas/Cat",
102+
},
103+
},
104+
},
105+
},
106+
},
107+
};
108+
109+
const responseType = getDataResponseType({
110+
responses,
111+
printNodes: (nodes) => nodes.map(print).join("\n"),
112+
});
113+
114+
expect(print(responseType)).toMatchInlineSnapshot(
115+
`"Schemas.Pet[] | Schemas.Cat[]"`
116+
);
117+
});
118+
119+
it("should returns undefined when no response", () => {
120+
const responses: ResponsesObject = {};
121+
122+
const responseType = getDataResponseType({
123+
responses,
124+
printNodes: (nodes) => nodes.map(print).join("\n"),
125+
});
126+
127+
expect(print(responseType)).toMatchInlineSnapshot(`"undefined"`);
128+
});
129+
});

plugins/typescript/src/core/getResponseType.ts renamed to plugins/typescript/src/core/getDataResponseType.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,15 @@ import { findCompatibleMediaType } from "./findCompatibleMediaType";
1313
import { getType } from "./schemaToTypeAliasDeclaration";
1414

1515
/**
16-
* Extract types from responses
16+
* Extract types from success responses (2xx)
1717
*/
18-
export const getResponseType = ({
18+
export const getDataResponseType = ({
1919
responses,
2020
components,
21-
filter,
2221
printNodes,
2322
}: {
2423
responses: ResponsesObject;
2524
components?: ComponentsObject;
26-
filter: (statusCode: string) => boolean;
2725
printNodes: (nodes: ts.Node[]) => string;
2826
}) => {
2927
const responseTypes = uniqBy(
@@ -32,7 +30,7 @@ export const getResponseType = ({
3230
mem,
3331
[statusCode, response]: [string, ResponseObject | ReferenceObject]
3432
) => {
35-
if (!filter(statusCode)) return mem;
33+
if (!statusCode.startsWith("2")) return mem;
3634
if (isReferenceObject(response)) {
3735
const [hash, topLevel, namespace, name] = response.$ref.split("/");
3836
if (hash !== "#" || topLevel !== "components") {

0 commit comments

Comments
 (0)