Skip to content

Commit 4705ceb

Browse files
authored
Handle error response types in spec (#37)
* update spec and regen * pull error response types from spec * forget it. let's be dumber * don't need that either * why mix promise and async/await * comment lamenting that Error is called Error
1 parent ade8870 commit 4705ceb

File tree

8 files changed

+857
-462
lines changed

8 files changed

+857
-462
lines changed

Api.ts

Lines changed: 175 additions & 249 deletions
Large diffs are not rendered by default.

OMICRON_VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
f523d0071335cfad68a5b1e765c76c87aa7db350
1+
7c76407859c0836d0cec1ddae25e13cb9eafc92a

generator/base/client.ts

Lines changed: 42 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,24 @@ export interface ApiConfig {
2727
customFetch?: typeof fetch;
2828
}
2929

30-
export interface HttpResponse<D extends unknown, E extends unknown = unknown>
31-
extends Response {
32-
data: D;
33-
error: E;
34-
}
30+
export type ErrorResponse = Response & {
31+
data: null;
32+
// Note that this Error is not JS `Error` but rather an Error type generated
33+
// from the spec. The fact that it has the same name as the global Error type
34+
// is unfortunate. If the generated error type disappears, this will not fail
35+
// typechecking here, but any code that depends on this having a certain shape
36+
// will fail, so it's not that bad, though the error message may be confusing.
37+
error: Error;
38+
};
39+
40+
export type SuccessResponse<Data extends unknown> = Response & {
41+
data: Data;
42+
error: null;
43+
};
44+
45+
export type ApiResponse<Data extends unknown> =
46+
| SuccessResponse<Data>
47+
| ErrorResponse;
3548

3649
type CancelToken = Symbol | string | number;
3750

@@ -101,14 +114,14 @@ export class HttpClient {
101114
}
102115
};
103116

104-
public request = async <T = any, E = any>({
117+
public request = async <Data extends unknown>({
105118
body,
106119
path,
107120
query,
108121
baseUrl,
109122
cancelToken,
110123
...params
111-
}: FullRequestParams): Promise<HttpResponse<T, E>> => {
124+
}: FullRequestParams): Promise<ApiResponse<Data>> => {
112125
const requestParams = this.mergeRequestParams(params);
113126
const queryString = query && toQueryString(query);
114127

@@ -118,39 +131,36 @@ export class HttpClient {
118131
url += "?" + queryString;
119132
}
120133

121-
return this.customFetch(url, {
134+
const response = await this.customFetch(url, {
122135
...requestParams,
123136
headers: {
124137
"Content-Type": "application/json",
125138
...requestParams.headers,
126139
},
127140
signal: cancelToken ? this.createAbortSignal(cancelToken) : void 0,
128141
body: JSON.stringify(snakeify(body)),
129-
}).then(async (response) => {
130-
const r = response as HttpResponse<T, E>;
131-
r.data = null as unknown as T;
132-
r.error = null as unknown as E;
133-
134-
await response
135-
.json()
136-
.then(processResponseBody)
137-
.then((data) => {
138-
if (r.ok) {
139-
r.data = data as T;
140-
} else {
141-
r.error = data as E;
142-
}
143-
})
144-
.catch((e) => {
145-
r.error = e;
146-
});
147-
148-
if (cancelToken) {
149-
this.abortControllers.delete(cancelToken);
142+
});
143+
144+
const r = response as ApiResponse<Data>;
145+
r.data = null as unknown as Data;
146+
r.error = null as unknown as Error;
147+
148+
try {
149+
const data = processResponseBody(await response.json());
150+
if (r.ok) {
151+
r.data = data as Data;
152+
} else {
153+
r.error = data as Error;
150154
}
155+
} catch (e) {
156+
r.error = e as Error;
157+
}
151158

152-
if (!r.ok) throw r;
153-
return r;
154-
});
159+
if (cancelToken) {
160+
this.abortControllers.delete(cancelToken);
161+
}
162+
163+
if (!r.ok) throw r;
164+
return r;
155165
};
156166
}

generator/base/util.test.ts

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -95,22 +95,24 @@ describe("parseIfDate", () => {
9595
test("snakeify", () => {
9696
const obj = {
9797
id: "vpc-id",
98-
timeCreated: new Date(2021, 0, 1).toISOString(),
99-
timeModified: new Date(2021, 0, 2).toISOString(),
98+
timeCreated: new Date(Date.UTC(2021, 0, 1)).toISOString(),
99+
timeModified: new Date(Date.UTC(2021, 0, 2)).toISOString(),
100100
systemRouterId: "router-id",
101101
nestedObj: {
102102
thereIsMore: 123,
103103
weAreSerious: "xyz",
104104
},
105105
};
106-
expect(snakeify(obj)).toEqual({
107-
id: "vpc-id",
108-
system_router_id: "router-id",
109-
time_created: "2021-01-01T05:00:00.000Z",
110-
time_modified: "2021-01-02T05:00:00.000Z",
111-
nested_obj: {
112-
there_is_more: 123,
113-
we_are_serious: "xyz",
114-
},
115-
});
106+
expect(snakeify(obj)).toMatchInlineSnapshot(`
107+
{
108+
"id": "vpc-id",
109+
"nested_obj": {
110+
"there_is_more": 123,
111+
"we_are_serious": "xyz",
112+
},
113+
"system_router_id": "router-id",
114+
"time_created": "2021-01-01T00:00:00.000Z",
115+
"time_modified": "2021-01-02T00:00:00.000Z",
116+
}
117+
`);
116118
});

generator/gen-client.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -313,7 +313,7 @@ $ npm install @oxidecomputer/api`,
313313
methodSpecDocs += `params);`;
314314
w(` params: RequestParams = {},
315315
) =>
316-
this.request<${successType}, any>({
316+
this.request<${successType}>({
317317
path: ${pathToTemplateStr(path)},
318318
method: "${method.toUpperCase()}",`);
319319
if (bodyType) {

generator/package-lock.json

Lines changed: 67 additions & 47 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)