Skip to content

Commit cb1522e

Browse files
committed
Improve errors
1 parent ba8050c commit cb1522e

16 files changed

+200
-109
lines changed

src/compression/Compression.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@ import { createKey, HttpHeader } from "../core/mod.ts";
33
import { ContentEncoding } from "./ContentEnconding.ts";
44
import { compress } from "./compress.ts";
55

6-
export interface ICompression {
6+
export interface TCompression {
77
readonly acceptedEncoding: readonly ContentEncoding[];
88
readonly usedEncoding: null | ContentEncoding;
99
}
1010

11-
export const CompressionKey: TKey<ICompression> = createKey<ICompression>(
11+
export const CompressionKey: TKey<TCompression> = createKey<TCompression>(
1212
"Compress",
1313
);
1414
export const CompressConsumer = CompressionKey.Consumer;
@@ -25,7 +25,7 @@ export function Compression(): Middleware {
2525
: [ContentEncoding.Identity];
2626

2727
const usedEncoding = selectEncoding(acceptedEncoding);
28-
const compressCtx: ICompression = {
28+
const compressCtx: TCompression = {
2929
acceptedEncoding,
3030
usedEncoding,
3131
};

src/core/HttpErreur.ts

Lines changed: 118 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -6,44 +6,55 @@ import type {
66
} from "./HttpStatus.ts";
77
import { HttpStatus } from "./HttpStatus.ts";
88

9-
export interface IHttpErreurData {
9+
export interface THttpErreurData {
1010
name: HttpStatusName;
1111
code: HttpStatusCode;
1212
message: HttpStatusMessage;
1313
}
1414

15-
const HttpErreurInternal: TErreurStore<IHttpErreurData> = createErreurStore<
16-
IHttpErreurData
15+
const HttpErreurInternal: TErreurStore<THttpErreurData> = createErreurStore<
16+
THttpErreurData
1717
>();
1818

1919
export const HttpErreur = HttpErreurInternal.asReadonly;
2020

21+
export function markHttpErreur(
22+
error: unknown,
23+
codeOrName: HttpStatusCode | HttpStatusName = 500,
24+
message?: HttpStatusMessage,
25+
): Error {
26+
return HttpErreurInternal.setAndReturn(
27+
error,
28+
resolveHttpErrorParams(codeOrName, message),
29+
);
30+
}
31+
2132
export function createHttpErreur(
2233
codeOrName: HttpStatusCode | HttpStatusName = 500,
23-
messageOrCause?: HttpStatusMessage | Error,
34+
message?: HttpStatusMessage,
35+
cause?: Error,
2436
): Error {
37+
const httpError = resolveHttpErrorParams(codeOrName, message);
38+
const error = new Error(`${httpError.message} (${httpError.name})`, {
39+
cause,
40+
});
41+
return HttpErreurInternal.setAndReturn(error, httpError);
42+
}
43+
44+
function resolveHttpErrorParams(
45+
codeOrName: HttpStatusCode | HttpStatusName = 500,
46+
message?: HttpStatusMessage,
47+
): THttpErreurData {
2548
const code: HttpStatusCode = typeof codeOrName === "number"
2649
? codeOrName
2750
: HttpStatus.fromName(codeOrName).code;
2851
const status = HttpStatus.fromCode(code);
29-
const messageStr = messageOrCause
30-
? typeof messageOrCause === "string"
31-
? messageOrCause
32-
: messageOrCause.message
33-
: undefined;
34-
const fullMessage = `${code} ${status.name}${
35-
messageStr ? `: ${messageStr}` : ""
36-
}`;
37-
const error = messageOrCause instanceof Error
38-
? messageOrCause
39-
: new Error(fullMessage);
40-
return HttpErreurInternal.setAndReturn(error, {
41-
code,
42-
name: status.name,
43-
message: messageStr ?? status.message,
44-
});
52+
const messageResolved = message ?? status.message;
53+
return { code, name: status.name, message: messageResolved };
4554
}
4655

56+
/** */
57+
4758
export type THttpErreurDetailsData =
4859
| { type: "Unauthorized"; reason?: string }
4960
| { type: "NotFound" }
@@ -59,82 +70,124 @@ const HttpErreurDetailsInternal: TErreurStore<THttpErreurDetailsData> =
5970

6071
export const HttpErreurDetails = HttpErreurDetailsInternal.asReadonly;
6172

62-
export function createUnauthorized(reason?: string): Error {
73+
export function markUnauthorized(error: unknown, reason?: string): Error {
74+
return HttpErreurDetailsInternal.setAndReturn(
75+
markHttpErreur(error, "Unauthorized", "Unauthorized"),
76+
{ type: "Unauthorized", reason },
77+
);
78+
}
79+
80+
export function createUnauthorized(reason?: string, cause?: Error): Error {
81+
return HttpErreurDetailsInternal.setAndReturn(
82+
createHttpErreur("Unauthorized", "Unauthorized", cause),
83+
{ type: "Unauthorized", reason },
84+
);
85+
}
86+
87+
export function markNotFound(error: unknown): Error {
6388
return HttpErreurDetailsInternal.setAndReturn(
64-
createHttpErreur("Unauthorized", "Unauthorized"),
65-
{
66-
type: "Unauthorized",
67-
reason,
68-
},
89+
markHttpErreur(error, "NotFound", "Not Found"),
90+
{ type: "NotFound" },
6991
);
7092
}
7193

72-
export function createNotFound(): Error {
94+
export function createNotFound(cause?: Error): Error {
7395
return HttpErreurDetailsInternal.setAndReturn(
74-
createHttpErreur("NotFound", "Not Found"),
96+
createHttpErreur("NotFound", "Not Found", cause),
7597
{ type: "NotFound" },
7698
);
7799
}
78100

79-
export function createNotAcceptable(): Error {
101+
export function markNotAcceptable(error: unknown): Error {
102+
return HttpErreurDetailsInternal.setAndReturn(
103+
markHttpErreur(error, "NotAcceptable", "Not Acceptable"),
104+
{ type: "NotAcceptable" },
105+
);
106+
}
107+
108+
export function createNotAcceptable(cause?: Error): Error {
80109
return HttpErreurDetailsInternal.setAndReturn(
81-
createHttpErreur("NotAcceptable", "Not Acceptable"),
82-
{
83-
type: "NotAcceptable",
84-
},
110+
createHttpErreur("NotAcceptable", "Not Acceptable", cause),
111+
{ type: "NotAcceptable" },
85112
);
86113
}
87114

88-
export function createBadRequest(message?: string): Error {
115+
export function markBadRequest(error: unknown, message?: string): Error {
89116
return HttpErreurDetailsInternal.setAndReturn(
90-
createHttpErreur("BadRequest", message ?? "Bad Request"),
91-
{
92-
type: "BadRequest",
93-
message,
94-
},
117+
markHttpErreur(error, "BadRequest", message ?? "Bad Request"),
118+
{ type: "BadRequest", message },
95119
);
96120
}
97121

98-
export function createForbidden(reason?: string): Error {
122+
export function createBadRequest(message?: string, cause?: Error): Error {
99123
return HttpErreurDetailsInternal.setAndReturn(
100-
createHttpErreur("Forbidden", "Forbidden"),
101-
{
102-
type: "Forbidden",
103-
reason,
104-
},
124+
createHttpErreur("BadRequest", message ?? "Bad Request", cause),
125+
{ type: "BadRequest", message },
126+
);
127+
}
128+
129+
export function markForbidden(error: unknown, reason?: string): Error {
130+
return HttpErreurDetailsInternal.setAndReturn(
131+
markHttpErreur(error, "Forbidden", "Forbidden"),
132+
{ type: "Forbidden", reason },
133+
);
134+
}
135+
136+
export function createForbidden(reason?: string, cause?: Error): Error {
137+
return HttpErreurDetailsInternal.setAndReturn(
138+
createHttpErreur("Forbidden", "Forbidden", cause),
139+
{ type: "Forbidden", reason },
140+
);
141+
}
142+
143+
export function markInternalServerError(
144+
error: unknown,
145+
message?: string,
146+
): Error {
147+
return HttpErreurDetailsInternal.setAndReturn(
148+
markHttpErreur(error, "InternalServerError", message),
149+
{ type: "InternalServerError", message },
105150
);
106151
}
107152

108153
export function createInternalServerError(
109-
messageOrCause?: string | Error,
154+
message?: string,
155+
cause?: Error,
156+
): Error {
157+
return HttpErreurDetailsInternal.setAndReturn(
158+
createHttpErreur("InternalServerError", message, cause),
159+
{ type: "InternalServerError", message },
160+
);
161+
}
162+
163+
export function markServerDidNotRespond(
164+
error: unknown,
165+
): Error {
166+
return HttpErreurDetailsInternal.setAndReturn(
167+
markHttpErreur(error, "InternalServerError", "Server did not respond"),
168+
{ type: "ServerDidNotRespond" },
169+
);
170+
}
171+
172+
export function createServerDidNotRespond(
173+
cause?: Error,
110174
): Error {
111175
return HttpErreurDetailsInternal.setAndReturn(
112-
createHttpErreur(
113-
"InternalServerError",
114-
messageOrCause ?? "Internal Server Error",
115-
),
116-
{
117-
type: "InternalServerError",
118-
message: messageOrCause instanceof Error ? undefined : messageOrCause,
119-
},
176+
createHttpErreur("InternalServerError", "Server did not respond", cause),
177+
{ type: "ServerDidNotRespond" },
120178
);
121179
}
122180

123-
export function createServerDidNotRespond(): Error {
181+
export function markTooManyRequests(error: unknown, reason?: string): Error {
124182
return HttpErreurDetailsInternal.setAndReturn(
125-
createHttpErreur("InternalServerError", "Server did not respond"),
126-
{
127-
type: "ServerDidNotRespond",
128-
},
183+
markHttpErreur(error, "TooManyRequests", reason),
184+
{ type: "TooManyRequests", reason },
129185
);
130186
}
131187

132-
export function createTooManyRequests(reason?: string): Error {
188+
export function createTooManyRequests(reason?: string, cause?: Error): Error {
133189
return HttpErreurDetailsInternal.setAndReturn(
134-
createHttpErreur("TooManyRequests"),
135-
{
136-
type: "TooManyRequests",
137-
reason,
138-
},
190+
createHttpErreur("TooManyRequests", reason, cause),
191+
{ type: "TooManyRequests", reason },
139192
);
140193
}

src/core/createHandler.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { createServerDidNotRespond } from "./HttpErreur.ts";
12
import { ZenContext } from "./ZenContext.ts";
23
import { ZenResponse } from "./ZenResponse.ts";
34
import type { Middleware } from "./compose.ts";
@@ -13,7 +14,7 @@ export function createHandler(middleware: Middleware): Handler {
1314
try {
1415
const zenContext = ZenContext.fromRequest(request);
1516
const zenResponse = await middleware(zenContext, () => {
16-
throw new Error("Server did not respond");
17+
throw createServerDidNotRespond();
1718
});
1819
const res = ZenResponse.toResponse(zenResponse);
1920
return res;

src/errors/ErrorToHttpError.ts

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,29 +3,22 @@ import type { Middleware } from "../core/mod.ts";
33
import { createInternalServerError, HttpErreur } from "../core/mod.ts";
44
import { LoggerConsumer } from "../logger/mod.ts";
55

6-
export type ErrorToHttpErrorOptions = {
7-
logOnError?: boolean;
8-
};
9-
106
/**
117
* Handle any error and convert it to an HttpError if it's not one
128
*/
13-
export function ErrorToHttpError(
14-
{ logOnError = true }: ErrorToHttpErrorOptions = {},
15-
): Middleware {
9+
export function ErrorToHttpError(): Middleware {
1610
return async (ctx, next) => {
1711
try {
1812
return await next(ctx);
1913
} catch (error) {
2014
const err = toError(error);
15+
const logger = ctx.get(LoggerConsumer);
2116
if (HttpErreur.has(err)) {
17+
logger.info(`Error is already an HttpError, skipping conversion`);
2218
throw err;
2319
}
24-
if (logOnError) {
25-
const logger = ctx.get(LoggerConsumer);
26-
logger.error(error);
27-
}
28-
throw createInternalServerError(err);
20+
logger.log(`Rewriting error to HttpError`);
21+
throw createInternalServerError(undefined, err);
2922
}
3023
};
3124
}

src/errors/HttpErrorToTextResponse.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,30 @@
11
import { toError } from "@dldc/erreur";
22
import type { Middleware } from "../core/mod.ts";
33
import { HttpErreur, ZenResponse } from "../core/mod.ts";
4+
import { LoggerConsumer } from "../logger/mod.ts";
5+
6+
export type HttpErrorToTextResponseOptions = {
7+
logOnError?: boolean;
8+
};
49

510
/**
611
* Handle HttpError and respond with a Text reponse
712
*/
8-
export function HttpErrorToTextResponse(): Middleware {
13+
export function HttpErrorToTextResponse(
14+
{ logOnError = false }: HttpErrorToTextResponseOptions = {},
15+
): Middleware {
916
return async (ctx, next) => {
1017
try {
1118
return await next(ctx);
1219
} catch (error) {
1320
const err = toError(error);
1421
const httpError = HttpErreur.get(err);
1522
if (httpError) {
23+
if (logOnError) {
24+
const logger = ctx.get(LoggerConsumer);
25+
logger.error(`Handled HttpError: ${httpError.message}`);
26+
logger.error(httpError);
27+
}
1628
return ZenResponse.create(
1729
`Error ${httpError.code} ${httpError.message}`,
1830
{ status: httpError.code },

src/errors/mod.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
export {
2-
ErrorToHttpError,
3-
type ErrorToHttpErrorOptions,
4-
} from "./ErrorToHttpError.ts";
1+
export { ErrorToHttpError } from "./ErrorToHttpError.ts";
52
export { HttpErrorToTextResponse } from "./HttpErrorToTextResponse.ts";
3+
export type { HttpErrorToTextResponseOptions } from "./HttpErrorToTextResponse.ts";
64
export { InvalidResponseToHttpError } from "./InvalidResponseToHttpError.ts";
5+
export type { InvalidResponseToHttpErrorOptions } from "./InvalidResponseToHttpError.ts";

src/zenjson/ZenjsonParser.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,16 @@ export const GetZenjsonBodyKey: TKey<GetZenjsonBody> = createKey<
1010
>("ZenjsonParser");
1111
export const GetZenjsonBodyKeyConsumer = GetZenjsonBodyKey.Consumer;
1212

13-
interface IZenjsonConfig {
13+
interface TZenjsonConfig {
1414
sanitize?: typeof zen.sanitize;
1515
restore?: typeof zen.restore;
1616
}
1717

18-
export const ZenjsonConfig: TKey<IZenjsonConfig> = createKey<IZenjsonConfig>(
18+
export const ZenjsonConfig: TKey<TZenjsonConfig> = createKey<TZenjsonConfig>(
1919
"ZenjsonConfig",
2020
);
2121

22-
export function ZenjsonParser(options: IZenjsonConfig = {}): Middleware {
22+
export function ZenjsonParser(options: TZenjsonConfig = {}): Middleware {
2323
const restore = options.restore ?? zen.restore;
2424

2525
return (ctx, next): Promise<any> => {

src/zenjson/zenjson.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,15 @@ import type { ZenContext, ZenResponse } from "../core/mod.ts";
33
import { json } from "../json/mod.ts";
44
import { ZenjsonConfig } from "./ZenjsonParser.ts";
55

6-
interface IOptions extends ResponseInit {
6+
export interface TZenjsonOptions extends ResponseInit {
77
sanitize?: typeof defaultSanitize;
88
context?: ZenContext;
99
}
1010

11-
export function zenjson<Data>(data: Data, options: IOptions = {}): ZenResponse {
11+
export function zenjson<Data>(
12+
data: Data,
13+
options: TZenjsonOptions = {},
14+
): ZenResponse {
1215
const sanitize = (() => {
1316
if (options.context) {
1417
const config = options.context.get(ZenjsonConfig.Consumer);

0 commit comments

Comments
 (0)