Skip to content

Commit 4b82e96

Browse files
committed
fix(runtime): add request info to HttpError
1 parent 897536f commit 4b82e96

File tree

6 files changed

+93
-14
lines changed

6 files changed

+93
-14
lines changed

.changeset/chilly-garlics-remain.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@ddadaal/next-typed-api-routes-runtime": patch
3+
---
4+
5+
add request info to HttpError

example/src/pages/errors.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import { type HttpError } from "@ddadaal/next-typed-api-routes-runtime";
2+
3+
export const formatHttpErrors = (e: HttpError[]) => JSON.stringify(e);

example/src/pages/index.tsx

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1+
import { failEvent, HttpError } from "@ddadaal/next-typed-api-routes-runtime/lib/client";
12
import type { NextPage } from "next";
2-
import { useState } from "react";
3+
import { useEffect, useState } from "react";
34
import { api } from "src/apis/api";
5+
import { formatHttpErrors } from "src/pages/errors";
46

57
export const ZodRouteTestDiv = () => {
68

@@ -93,6 +95,36 @@ export const AjvRouteTestDiv = () => {
9395
);
9496
};
9597

98+
export const FailEventTestDiv = () => {
99+
100+
const [errors, setErrors] = useState<HttpError[]>([]);
101+
102+
useEffect(() => {
103+
const handler = (e: HttpError) => {
104+
setErrors((errors) => [...errors, e]);
105+
};
106+
failEvent.register(handler);
107+
return () => failEvent.unregister(handler);
108+
}, []);
109+
110+
111+
return (
112+
<div id="errors">
113+
<button onClick={async () => {
114+
await api.zodRoute({ body: { error: true }, query: { test: "123" } });
115+
}}>
116+
Call Error zodRoute
117+
</button>
118+
{errors.length > 0 ? (
119+
<div>
120+
{formatHttpErrors(errors)}
121+
</div>
122+
) : undefined}
123+
</div>
124+
);
125+
126+
};
127+
96128
const Home: NextPage = () => {
97129

98130

@@ -108,6 +140,9 @@ const Home: NextPage = () => {
108140
<div>
109141
<TypeboxRouteTestDiv />
110142
</div>
143+
<div>
144+
<FailEventTestDiv />
145+
</div>
111146
</div>
112147
);
113148
};

example/tests/apis.test.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1+
import { HttpError } from "@ddadaal/next-typed-api-routes-runtime";
12
import { expect, test } from "@playwright/test";
3+
import { formatHttpErrors } from "src/pages/errors";
24

35
import { api } from "../src/apis/api";
46

@@ -71,7 +73,7 @@ test("should call login", async () => {
7173
});
7274

7375
test("should validate email", async () => {
74-
const resp = await api.login({ query: { password: "123", username: "123" } })
76+
const resp = await api.login({ query: { password: "123", username: "123" } })
7577
.httpError(400, (err: any) => {
7678
expect(err.code).toBe("QUERY_VALIDATION_ERROR");
7779
return err;
@@ -104,3 +106,20 @@ test("should handle error in browser", async ({ page }) => {
104106

105107
expect(await text?.innerText()).toBe("401 NotExists");
106108
});
109+
110+
test("should handle event", async ({ page }) => {
111+
await page.goto("http://localhost:3000");
112+
113+
await page.click("#errors button");
114+
115+
const text = await page.waitForSelector("#errors > div");
116+
117+
expect(await text?.innerText()).toBe(formatHttpErrors([
118+
new HttpError(404, { error: "123" }, JSON.stringify({ error: "123" }), {
119+
method: "POST",
120+
url: "/api/zodRoute/123",
121+
body: { error: true },
122+
query: {},
123+
}),
124+
]));
125+
});
Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,28 @@
1+
import { HttpMethod } from "./fetch";
2+
13
export const FETCH_ERROR = -2;
24
export const TYPE_ERROR = -1;
35

6+
export interface OriginalRequest {
7+
method: HttpMethod;
8+
url: string;
9+
query?: unknown;
10+
body?: unknown;
11+
headers?: Record<string, string>;
12+
}
13+
414
/** A unified class for all errors */
515
export class HttpError<T = any> {
6-
data: T;
7-
status: number;
8-
text?: string;
916

1017
/** A Server Error */
1118
get isFetchError() { return this.status === FETCH_ERROR; }
1219
get isTypeError() { return this.status === TYPE_ERROR; }
1320

14-
constructor(status: number, data: T, text?: string) {
15-
this.data = data;
16-
this.status = status;
17-
this.text = text;
21+
constructor(
22+
public status: number,
23+
public data: T,
24+
public text?: string,
25+
public request?: OriginalRequest,
26+
) {
1827
}
1928
}

packages/runtime/src/fetch/fetch.ts

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import type { Querystring } from "../types/request";
22
import type { AnySchema, SuccessResponse } from "../types/schema";
33
import { failEvent, finallyEvent, prefetchEvent, successEvent } from "./events";
4-
import { FETCH_ERROR, HttpError, TYPE_ERROR } from "./HttpError";
4+
import { FETCH_ERROR, HttpError, OriginalRequest, TYPE_ERROR } from "./HttpError";
55
import { parseQueryToQuerystring } from "./parseQueryToQuerystring";
66

77
function isServer() {
@@ -65,12 +65,15 @@ implements PromiseLike<SuccessResponse<T>> {
6565

6666
private promise: Promise<Response>;
6767
private httpErrorHandler: Map<number, (error: any) => unknown>;
68+
private request: OriginalRequest;
6869

6970
constructor(
7071
promise: Promise<Response>,
72+
request: OriginalRequest,
7173
) {
7274
this.promise = promise;
7375
this.httpErrorHandler = new Map();
76+
this.request = request;
7477
}
7578

7679
then<TSuc = SuccessResponse<T>, TRej = never>(
@@ -93,8 +96,9 @@ implements PromiseLike<SuccessResponse<T>> {
9396
throw resp;
9497
}
9598
} else {
99+
96100
const text = await resp.text();
97-
const payload = new HttpError(resp.status, tryParseJson(text), text);
101+
const payload = new HttpError(resp.status, tryParseJson(text), text, this.request);
98102

99103
const handler = this.httpErrorHandler.get(resp.status);
100104

@@ -119,10 +123,10 @@ implements PromiseLike<SuccessResponse<T>> {
119123
};
120124

121125
if (r.name === "FetchError") {
122-
const payload = new HttpError(FETCH_ERROR, r);
126+
const payload = new HttpError(FETCH_ERROR, r, undefined, this.request);
123127
failEvent.execute(payload);
124128
} else if (r instanceof TypeError) {
125-
const payload = new HttpError(TYPE_ERROR, r);
129+
const payload = new HttpError(TYPE_ERROR, r, undefined, this.request);
126130
failEvent.execute(payload);
127131
}
128132

@@ -192,7 +196,11 @@ export function jsonFetch<T extends AnySchema>(
192196
},
193197
body: isForm ? (info.body as any) : JSON.stringify(info.body),
194198
signal,
195-
}));
199+
}), {
200+
method: info.method ?? "GET", url: info.url,
201+
body: info.body, headers: info.headers,
202+
query: info.query,
203+
});
196204
}
197205

198206
export type JsonFetch = typeof jsonFetch;

0 commit comments

Comments
 (0)