@setu-ts/exceptions 0.9.0 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENT_CARD.md +50 -0
- package/README.md +9 -1
- package/dist/errors/exceptions.d.ts.map +1 -1
- package/dist/errors/exceptions.js.map +1 -1
- package/dist/formatters/problem-details.d.ts.map +1 -1
- package/dist/formatters/problem-details.js.map +1 -1
- package/dist/middleware/error-handler.d.ts +2 -3
- package/dist/middleware/error-handler.d.ts.map +1 -1
- package/dist/middleware/error-handler.js +19 -8
- package/dist/middleware/error-handler.js.map +1 -1
- package/dist/middleware/error-responder-impl.d.ts +5 -0
- package/dist/middleware/error-responder-impl.d.ts.map +1 -1
- package/dist/middleware/error-responder-impl.js +18 -8
- package/dist/middleware/error-responder-impl.js.map +1 -1
- package/package.json +14 -13
- package/src/errors/exceptions.ts +1 -4
- package/src/formatters/problem-details.ts +4 -3
- package/src/middleware/error-handler.ts +35 -15
- package/src/middleware/error-responder-impl.ts +22 -8
package/AGENT_CARD.md
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# @setu-ts/exceptions — HTTP errors and error responses
|
|
2
|
+
|
|
3
|
+
**Add it:** REST scaffolds install this package; there is no `setu add` entry or capability. In
|
|
4
|
+
`setu.config.ts`, register `errorHandler` on the global middleware pipeline.
|
|
5
|
+
|
|
6
|
+
## Format thrown errors consistently
|
|
7
|
+
|
|
8
|
+
```ts
|
|
9
|
+
import { errorHandler } from "@setu-ts/exceptions";
|
|
10
|
+
|
|
11
|
+
app.middleware.add(errorHandler({ format: "rfc9457" }), {
|
|
12
|
+
priority: 0,
|
|
13
|
+
name: "error-handler",
|
|
14
|
+
});
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Refuse a request with an HTTP error
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
import type { IRouterApi } from "@setu-ts/common";
|
|
21
|
+
import { notFound } from "@setu-ts/exceptions";
|
|
22
|
+
|
|
23
|
+
export function registerUserRoutes(router: IRouterApi): void {
|
|
24
|
+
router.get("/users/:id", {
|
|
25
|
+
handler: (ctx) => {
|
|
26
|
+
throw notFound(`User ${ctx.params.id} not found`);
|
|
27
|
+
},
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Behaviour the types do not show
|
|
33
|
+
|
|
34
|
+
- Priority `0` makes the handler wrap downstream middleware and routes; it catches their throws.
|
|
35
|
+
- `format` defaults to `"default"`; `"rfc9457"` sends `application/problem+json`.
|
|
36
|
+
- `maskInternalErrors` defaults to `true`: unexpected 5xx messages become the status title;
|
|
37
|
+
deliberately thrown `HttpError` messages are preserved.
|
|
38
|
+
- `includeStackTrace` defaults to `false`; masked errors never expose a stack even when enabled.
|
|
39
|
+
- `logErrors` defaults to `true`: warn below 500, error otherwise. Without a logger, only 5xx
|
|
40
|
+
reaches the runtime error sink.
|
|
41
|
+
- `respond` sees the normalized, masked error and resolved status. A returned response owns its
|
|
42
|
+
status, headers and body; `undefined` falls through to the configured formatter.
|
|
43
|
+
|
|
44
|
+
## Other options
|
|
45
|
+
|
|
46
|
+
- `badRequest`, `unauthorized`, `forbidden`, `conflict`, `tooManyRequests` and `serviceUnavailable`
|
|
47
|
+
create other HTTP refusals.
|
|
48
|
+
- `validationError` stores field failures in `details.errors`; `internalServerError` accepts a
|
|
49
|
+
cause.
|
|
50
|
+
- `format` also accepts a formatter function or the deprecated `"rfc7807"` arm.
|
package/README.md
CHANGED
|
@@ -30,6 +30,14 @@ app.router.get('/users/:id', async (ctx) => {
|
|
|
30
30
|
|
|
31
31
|
## Options
|
|
32
32
|
|
|
33
|
+
Caught errors produce one diagnostic: `warn` for a served status below 500, `error` otherwise. The
|
|
34
|
+
request logger records completion rather than a second failure diagnostic. With no registered
|
|
35
|
+
logger, handled 5xx goes to `IRuntimeServices.reportError`, the runtime's last-resort JSON stderr
|
|
36
|
+
sink (Workers uses its platform log channel); handled 4xx stays silent. `logErrors: false` disables
|
|
37
|
+
both paths. The responder's optional, pure `statusFor(error)` lets request logging predict this
|
|
38
|
+
middleware's status without reporting a clamp twice. A custom `respond` hook's different status is
|
|
39
|
+
not reflected in request completion logs.
|
|
40
|
+
|
|
33
41
|
`errorHandler(options?)` takes:
|
|
34
42
|
|
|
35
43
|
| Option | Type | Default | Description |
|
|
@@ -170,7 +178,7 @@ problem type identified by `https://setu-ts.dev/errors/validation` — the same
|
|
|
170
178
|
| `ErrorFormat` | type |
|
|
171
179
|
| `ErrorHandlerFormatter` | type |
|
|
172
180
|
|
|
173
|
-
Generated from the package barrel by `
|
|
181
|
+
Generated from the package barrel by `pnpm run docs:exports`; `pnpm run check:docs` fails when it
|
|
174
182
|
drifts.
|
|
175
183
|
|
|
176
184
|
## Full API
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"exceptions.d.ts","sourceRoot":"","sources":["../../src/errors/exceptions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAMvD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,aAAa,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAe1D,CAAC;AAEF;;;;;;;GAOG;AACH,wBAAgB,WAAW,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAEtD;AAMD;;;;;;;GAOG;AACH,wBAAgB,UAAU,CACxB,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GAC1C,SAAS,CAEX;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,CAEvD;AAED;;;;;;GAMG;AACH,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,CAEpD;AAED;;;;;;GAMG;AACH,wBAAgB,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,CAEnD;AAED;;;;;;GAMG;AACH,wBAAgB,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,CAEnD;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,eAAe,
|
|
1
|
+
{"version":3,"file":"exceptions.d.ts","sourceRoot":"","sources":["../../src/errors/exceptions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAMvD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,aAAa,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAe1D,CAAC;AAEF;;;;;;;GAOG;AACH,wBAAgB,WAAW,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAEtD;AAMD;;;;;;;GAOG;AACH,wBAAgB,UAAU,CACxB,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GAC1C,SAAS,CAEX;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,CAEvD;AAED;;;;;;GAMG;AACH,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,CAEpD;AAED;;;;;;GAMG;AACH,wBAAgB,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,CAEnD;AAED;;;;;;GAMG;AACH,wBAAgB,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,CAEnD;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,SAAS,eAAe,EAAE,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAG/F;AAED;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAC7B,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GAC1C,SAAS,CAEX;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,KAAK,GAAG,SAAS,CAE7E;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,CAEzD;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,CAE7D"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"exceptions.js","sourceRoot":"","sources":["../../src/errors/exceptions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAG5C,8EAA8E;AAC9E,6EAA6E;AAC7E,8EAA8E;AAE9E;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,CAAC,MAAM,aAAa,GAAqC;IAC7D,GAAG,EAAE,aAAa;IAClB,GAAG,EAAE,cAAc;IACnB,GAAG,EAAE,WAAW;IAChB,GAAG,EAAE,WAAW;IAChB,GAAG,EAAE,UAAU;IACf,GAAG,EAAE,mBAAmB;IACxB,GAAG,EAAE,wBAAwB;IAC7B,GAAG,EAAE,sBAAsB;IAC3B,GAAG,EAAE,mBAAmB;IACxB,GAAG,EAAE,uBAAuB;IAC5B,GAAG,EAAE,iBAAiB;IACtB,GAAG,EAAE,aAAa;IAClB,GAAG,EAAE,qBAAqB;IAC1B,GAAG,EAAE,iBAAiB;CACvB,CAAC;AAEF;;;;;;;GAOG;AACH,MAAM,UAAU,WAAW,CAAC,UAAkB;IAC5C,OAAO,aAAa,CAAC,UAAU,CAAC,IAAI,OAAO,CAAC;AAC9C,CAAC;AAED,8EAA8E;AAC9E,YAAY;AACZ,8EAA8E;AAE9E;;;;;;;GAOG;AACH,MAAM,UAAU,UAAU,CACxB,OAAe,EACf,OAA2C;IAE3C,OAAO,IAAI,SAAS,CAAC,GAAG,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;AAC9C,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,YAAY,CAAC,OAAe;IAC1C,OAAO,IAAI,SAAS,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;AACrC,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,SAAS,CAAC,OAAe;IACvC,OAAO,IAAI,SAAS,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;AACrC,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,QAAQ,CAAC,OAAe;IACtC,OAAO,IAAI,SAAS,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;AACrC,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,QAAQ,CAAC,OAAe;IACtC,OAAO,IAAI,SAAS,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;AACrC,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,eAAe,
|
|
1
|
+
{"version":3,"file":"exceptions.js","sourceRoot":"","sources":["../../src/errors/exceptions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAG5C,8EAA8E;AAC9E,6EAA6E;AAC7E,8EAA8E;AAE9E;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,CAAC,MAAM,aAAa,GAAqC;IAC7D,GAAG,EAAE,aAAa;IAClB,GAAG,EAAE,cAAc;IACnB,GAAG,EAAE,WAAW;IAChB,GAAG,EAAE,WAAW;IAChB,GAAG,EAAE,UAAU;IACf,GAAG,EAAE,mBAAmB;IACxB,GAAG,EAAE,wBAAwB;IAC7B,GAAG,EAAE,sBAAsB;IAC3B,GAAG,EAAE,mBAAmB;IACxB,GAAG,EAAE,uBAAuB;IAC5B,GAAG,EAAE,iBAAiB;IACtB,GAAG,EAAE,aAAa;IAClB,GAAG,EAAE,qBAAqB;IAC1B,GAAG,EAAE,iBAAiB;CACvB,CAAC;AAEF;;;;;;;GAOG;AACH,MAAM,UAAU,WAAW,CAAC,UAAkB;IAC5C,OAAO,aAAa,CAAC,UAAU,CAAC,IAAI,OAAO,CAAC;AAC9C,CAAC;AAED,8EAA8E;AAC9E,YAAY;AACZ,8EAA8E;AAE9E;;;;;;;GAOG;AACH,MAAM,UAAU,UAAU,CACxB,OAAe,EACf,OAA2C;IAE3C,OAAO,IAAI,SAAS,CAAC,GAAG,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;AAC9C,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,YAAY,CAAC,OAAe;IAC1C,OAAO,IAAI,SAAS,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;AACrC,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,SAAS,CAAC,OAAe;IACvC,OAAO,IAAI,SAAS,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;AACrC,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,QAAQ,CAAC,OAAe;IACtC,OAAO,IAAI,SAAS,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;AACrC,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,QAAQ,CAAC,OAAe;IACtC,OAAO,IAAI,SAAS,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;AACrC,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,eAAe,CAAC,MAAkC,EAAE,OAAgB;IAClF,MAAM,OAAO,GAAG,OAAO,IAAI,0BAA0B,MAAM,CAAC,MAAM,YAAY,CAAC;IAC/E,OAAO,IAAI,SAAS,CAAC,GAAG,EAAE,OAAO,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC;AACjD,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,eAAe,CAC7B,OAAe,EACf,OAA2C;IAE3C,OAAO,IAAI,SAAS,CAAC,GAAG,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;AAC9C,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,mBAAmB,CAAC,OAAe,EAAE,KAAa;IAChE,OAAO,IAAI,SAAS,CAAC,GAAG,EAAE,OAAO,EAAE,SAAS,EAAE,KAAK,CAAC,CAAC;AACvD,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,cAAc,CAAC,OAAe;IAC5C,OAAO,IAAI,SAAS,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;AACrC,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,kBAAkB,CAAC,OAAe;IAChD,OAAO,IAAI,SAAS,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;AACrC,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"problem-details.d.ts","sourceRoot":"","sources":["../../src/formatters/problem-details.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAKvD;;;;GAIG;AACH,eAAO,MAAM,eAAe,+BAA+B,CAAC;AAE5D;;;;;;;;;GASG;AACH,eAAO,MAAM,eAAe,EAAE,MAAwC,CAAC;AAEvE;;;;;GAKG;AACH,eAAO,MAAM,WAAW,gBAAgB,CAAC;AAEzC;;;;;;;;GAQG;AACH,MAAM,WAAW,cAAc;IAC7B,oDAAoD;IACpD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,2DAA2D;IAC3D,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,0DAA0D;IAC1D,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,gEAAgE;IAChE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,0EAA0E;IAC1E,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,yEAAyE;IACzE,QAAQ,CAAC,MAAM,CAAC,EAAE,aAAa,CAAC;QAC9B,KAAK,EAAE,MAAM,CAAC;QACd,OAAO,EAAE,MAAM,CAAC;QAChB,IAAI,CAAC,EAAE,MAAM,CAAC;KACf,CAAC,CAAC;IACH,0EAA0E;IAC1E,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,yDAAyD;IACzD,QAAQ,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACjC;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,gBAAgB,EAAE,OAAO,MAAmD,CAAC;AAE1F;;;;;;;GAOG;AACH,MAAM,MAAM,mBAAmB,GAAG,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,KAAK,MAAM,CAAC;AAgBrF;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,KAAK,EACZ,GAAG,EAAE,eAAe,GAAG,SAAS,EAChC,WAAW,EAAE,mBAAmB,GAC/B,cAAc,
|
|
1
|
+
{"version":3,"file":"problem-details.d.ts","sourceRoot":"","sources":["../../src/formatters/problem-details.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAKvD;;;;GAIG;AACH,eAAO,MAAM,eAAe,+BAA+B,CAAC;AAE5D;;;;;;;;;GASG;AACH,eAAO,MAAM,eAAe,EAAE,MAAwC,CAAC;AAEvE;;;;;GAKG;AACH,eAAO,MAAM,WAAW,gBAAgB,CAAC;AAEzC;;;;;;;;GAQG;AACH,MAAM,WAAW,cAAc;IAC7B,oDAAoD;IACpD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,2DAA2D;IAC3D,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,0DAA0D;IAC1D,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,gEAAgE;IAChE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,0EAA0E;IAC1E,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,yEAAyE;IACzE,QAAQ,CAAC,MAAM,CAAC,EAAE,aAAa,CAAC;QAC9B,KAAK,EAAE,MAAM,CAAC;QACd,OAAO,EAAE,MAAM,CAAC;QAChB,IAAI,CAAC,EAAE,MAAM,CAAC;KACf,CAAC,CAAC;IACH,0EAA0E;IAC1E,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,yDAAyD;IACzD,QAAQ,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACjC;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,gBAAgB,EAAE,OAAO,MAAmD,CAAC;AAE1F;;;;;;;GAOG;AACH,MAAM,MAAM,mBAAmB,GAAG,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,KAAK,MAAM,CAAC;AAgBrF;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,KAAK,EACZ,GAAG,EAAE,eAAe,GAAG,SAAS,EAChC,WAAW,EAAE,mBAAmB,GAC/B,cAAc,CAgChB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"problem-details.js","sourceRoot":"","sources":["../../src/formatters/problem-details.ts"],"names":[],"mappings":"AAgBA,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AACtD,OAAO,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AAEpD;;;;GAIG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,4BAA4B,CAAC;AAE5D;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,eAAe,GAAW,GAAG,eAAe,aAAa,CAAC;AAEvE;;;;;GAKG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,aAAa,CAAC;AAkCzC;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAkB,MAAM,CAAC,kCAAkC,CAAC,CAAC;AAY1F;;;;;GAKG;AACH,SAAS,aAAa,CAAC,KAAgB;IACrC,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC;IAC9B,IAAI,OAAO,KAAK,SAAS,IAAI,CAAC,CAAC,QAAQ,IAAI,OAAO,CAAC,EAAE,CAAC;QACpD,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,OAAO,OAAO,CAAC,MAAkC,CAAC;AACpD,CAAC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,mBAAmB,CACjC,KAAY,EACZ,GAAgC,EAChC,WAAgC;IAEhC,MAAM,MAAM,GAAG,KAAK,YAAY,SAAS,CAAC;IAC1C,MAAM,UAAU,GAAG,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC,GAAG,CAAC;IACnD,MAAM,MAAM,GAAG,MAAM,CAAC,CAAC,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IACzD,0EAA0E;IAC1E,2EAA2E;IAC3E,2EAA2E;IAC3E,sBAAsB;IACtB,EAAE;IACF,0EAA0E;IAC1E,6EAA6E;IAC7E,sEAAsE;IACtE,kDAAkD;IAClD,yEAAyE;IACzE,2EAA2E;IAC3E,2EAA2E;IAC3E,4EAA4E;IAC5E,2CAA2C;IAC3C,MAAM,aAAa,
|
|
1
|
+
{"version":3,"file":"problem-details.js","sourceRoot":"","sources":["../../src/formatters/problem-details.ts"],"names":[],"mappings":"AAgBA,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AACtD,OAAO,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AAEpD;;;;GAIG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,4BAA4B,CAAC;AAE5D;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,eAAe,GAAW,GAAG,eAAe,aAAa,CAAC;AAEvE;;;;;GAKG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,aAAa,CAAC;AAkCzC;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAkB,MAAM,CAAC,kCAAkC,CAAC,CAAC;AAY1F;;;;;GAKG;AACH,SAAS,aAAa,CAAC,KAAgB;IACrC,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC;IAC9B,IAAI,OAAO,KAAK,SAAS,IAAI,CAAC,CAAC,QAAQ,IAAI,OAAO,CAAC,EAAE,CAAC;QACpD,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,OAAO,OAAO,CAAC,MAAkC,CAAC;AACpD,CAAC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,mBAAmB,CACjC,KAAY,EACZ,GAAgC,EAChC,WAAgC;IAEhC,MAAM,MAAM,GAAG,KAAK,YAAY,SAAS,CAAC;IAC1C,MAAM,UAAU,GAAG,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC,GAAG,CAAC;IACnD,MAAM,MAAM,GAAG,MAAM,CAAC,CAAC,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IACzD,0EAA0E;IAC1E,2EAA2E;IAC3E,2EAA2E;IAC3E,sBAAsB;IACtB,EAAE;IACF,0EAA0E;IAC1E,6EAA6E;IAC7E,sEAAsE;IACtE,kDAAkD;IAClD,yEAAyE;IACzE,2EAA2E;IAC3E,2EAA2E;IAC3E,4EAA4E;IAC5E,2CAA2C;IAC3C,MAAM,aAAa,GACjB,MAAM,IAAI,gBAAgB,IAAI,KAAK;QACjC,CAAC,CAAE,KAAsD,CAAC,gBAAgB,CAAC;QAC3E,CAAC,CAAC,SAAS,CAAC;IAChB,MAAM,MAAM,GAAG,OAAO,aAAa,KAAK,QAAQ,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC;IAEjF,OAAO;QACL,IAAI,EAAE,WAAW,CAAC,UAAU,EAAE,MAAM,KAAK,SAAS,CAAC;QACnD,KAAK,EAAE,WAAW,CAAC,UAAU,CAAC;QAC9B,MAAM,EAAE,UAAU;QAClB,MAAM;QACN,GAAG,CAAC,GAAG,KAAK,SAAS,IAAI,EAAE,QAAQ,EAAE,GAAG,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC;QACxD,GAAG,CAAC,MAAM,KAAK,SAAS,IAAI,EAAE,MAAM,EAAE,CAAC;KACxC,CAAC;AACJ,CAAC"}
|
|
@@ -67,9 +67,8 @@ export interface ErrorHandlerOptions {
|
|
|
67
67
|
*/
|
|
68
68
|
readonly maskInternalErrors?: boolean;
|
|
69
69
|
/**
|
|
70
|
-
* When `true` (the default),
|
|
71
|
-
*
|
|
72
|
-
* registered. When no logger is present, logging is silently skipped.
|
|
70
|
+
* When `true` (the default), logs once at `warn` below 500 and `error`
|
|
71
|
+
* otherwise. Without a logger, only 5xx reaches runtime.reportError.
|
|
73
72
|
*/
|
|
74
73
|
readonly logErrors?: boolean;
|
|
75
74
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"error-handler.d.ts","sourceRoot":"","sources":["../../src/middleware/error-handler.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"error-handler.d.ts","sourceRoot":"","sources":["../../src/middleware/error-handler.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,OAAO,KAAK,EACV,aAAa,EAEb,eAAe,EAEf,kBAAkB,EACnB,MAAM,iBAAiB,CAAC;AAUzB,OAAO,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AAOpD,OAAO,EACL,KAAK,WAAW,EAChB,KAAK,qBAAqB,EAE3B,MAAM,kCAAkC,CAAC;AAI1C;;;;GAIG;AACH,MAAM,WAAW,mBAAmB;IAClC;;;OAGG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,qBAAqB,CAAC;IACtD;;;;;;;;;;;;;;;OAeG;IACH,QAAQ,CAAC,iBAAiB,CAAC,EAAE,OAAO,CAAC;IACrC;;;;;;;;;;;;;OAaG;IACH,QAAQ,CAAC,kBAAkB,CAAC,EAAE,OAAO,CAAC;IACtC;;;OAGG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC;IAC7B;;;;;;;;;;;OAWG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,CACjB,KAAK,EAAE,SAAS,EAChB,GAAG,EAAE,eAAe,KACjB,aAAa,GAAG,SAAS,GAAG,OAAO,CAAC,aAAa,GAAG,SAAS,CAAC,CAAC;CACrE;AAuBD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,YAAY,CAAC,OAAO,CAAC,EAAE,mBAAmB,GAAG,kBAAkB,CAkK9E"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { brandErrorResponder, CAPABILITIES, ERROR_RESPONDER_STATE_KEY, httpStatusHintOf, resolveResponseStatus, serializeError, } from '@setu-ts/common';
|
|
2
2
|
import { HttpError } from "../errors/http-error.js";
|
|
3
3
|
import { internalServerError, statusTitle } from "../errors/exceptions.js";
|
|
4
|
-
import { buildErrorFromInit, createErrorResponder } from "./error-responder-impl.js";
|
|
4
|
+
import { buildErrorFromInit, createErrorResponder, thrownErrorStatus, } from "./error-responder-impl.js";
|
|
5
5
|
import { selectFormatter, } from "../formatters/error-formatter.js";
|
|
6
6
|
import { rfc9457Formatter } from "../formatters/rfc9457-formatter.js";
|
|
7
7
|
import { rfc7807Formatter } from "../formatters/rfc7807-formatter.js";
|
|
@@ -70,7 +70,9 @@ export function errorHandler(options) {
|
|
|
70
70
|
catch (rawError) {
|
|
71
71
|
// Normalize: HttpError passes through, anything else becomes a 500.
|
|
72
72
|
const isHttpError = rawError instanceof HttpError;
|
|
73
|
-
const error = isHttpError
|
|
73
|
+
const error = isHttpError
|
|
74
|
+
? rawError
|
|
75
|
+
: internalServerError(rawError instanceof Error ? rawError.message : 'Internal Server Error', rawError instanceof Error ? rawError : undefined);
|
|
74
76
|
// A thrower that cannot import this package states the status its own
|
|
75
77
|
// error means by branding it with an `HttpStatusHint` (M89b) — the seam
|
|
76
78
|
// `@setu-ts/database-plugin` uses for its query-shape refusals, which
|
|
@@ -116,7 +118,7 @@ export function errorHandler(options) {
|
|
|
116
118
|
// the write also makes `responseError === error` false, which suppresses
|
|
117
119
|
// the stack — correct by that check's own stated invariant, since the
|
|
118
120
|
// error being SERVED is no longer the error that was LOGGED.
|
|
119
|
-
const servedStatus = resolveResponseStatus(
|
|
121
|
+
const servedStatus = resolveResponseStatus(thrownErrorStatus(rawError).requested, ctx);
|
|
120
122
|
if (servedStatus !== responseError.statusCode) {
|
|
121
123
|
responseError = new HttpError(servedStatus, responseError.message, responseError.details, responseError.cause instanceof Error ? responseError.cause : undefined);
|
|
122
124
|
}
|
|
@@ -144,8 +146,7 @@ export function errorHandler(options) {
|
|
|
144
146
|
}
|
|
145
147
|
return result;
|
|
146
148
|
}
|
|
147
|
-
// Log the
|
|
148
|
-
// and the cause chain regardless of masking — and report the status the
|
|
149
|
+
// Log the unmasked diagnostic and cause chain, with the status the
|
|
149
150
|
// formatter below will actually serve. A hint may change a normalized
|
|
150
151
|
// `500` to `501`, so logging the pre-hint status would mislead operators.
|
|
151
152
|
if (logErrors) {
|
|
@@ -187,8 +188,8 @@ export function errorHandler(options) {
|
|
|
187
188
|
return handleError;
|
|
188
189
|
}
|
|
189
190
|
/**
|
|
190
|
-
* Logs
|
|
191
|
-
*
|
|
191
|
+
* Logs once through the registered logger, or the runtime sink for 5xx when
|
|
192
|
+
* no logger is registered. Handled 4xx without a logger stays silent.
|
|
192
193
|
*
|
|
193
194
|
* @param ctx - The request context
|
|
194
195
|
* @param error - The error to log, carrying the unmasked message and cause
|
|
@@ -196,12 +197,22 @@ export function errorHandler(options) {
|
|
|
196
197
|
*/
|
|
197
198
|
function logError(ctx, error, statusCode) {
|
|
198
199
|
if (!ctx.services.has(CAPABILITIES.LOGGER)) {
|
|
200
|
+
if (statusCode >= 500 && ctx.services.has(CAPABILITIES.RUNTIME)) {
|
|
201
|
+
ctx.services.get(CAPABILITIES.RUNTIME).reportError?.({
|
|
202
|
+
level: 'error',
|
|
203
|
+
...serializeError(error),
|
|
204
|
+
statusCode,
|
|
205
|
+
requestId: ctx.id,
|
|
206
|
+
method: ctx.request.method,
|
|
207
|
+
path: ctx.request.path,
|
|
208
|
+
});
|
|
209
|
+
}
|
|
199
210
|
return;
|
|
200
211
|
}
|
|
201
212
|
const logger = ctx.services.get(CAPABILITIES.LOGGER);
|
|
202
213
|
// Serialize the cause (X2-5): a raw `Error` in log metadata renders as `{}`
|
|
203
214
|
// under `JSON.stringify` because `message`/`stack` are non-enumerable.
|
|
204
|
-
logger
|
|
215
|
+
logger[statusCode < 500 ? 'warn' : 'error'](error.message, {
|
|
205
216
|
statusCode,
|
|
206
217
|
requestId: ctx.id,
|
|
207
218
|
...(error.cause !== undefined && { cause: serializeError(error.cause) }),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"error-handler.js","sourceRoot":"","sources":["../../src/middleware/error-handler.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"error-handler.js","sourceRoot":"","sources":["../../src/middleware/error-handler.ts"],"names":[],"mappings":"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"}
|
|
@@ -13,6 +13,11 @@
|
|
|
13
13
|
import type { ErrorResponseInit, IErrorResponder } from '@setu-ts/common';
|
|
14
14
|
import { HttpError } from '../errors/http-error.ts';
|
|
15
15
|
import type { ErrorHandlerFormatter } from '../formatters/error-formatter.ts';
|
|
16
|
+
/** The pure thrown-value rule, shared by serving and request-log prediction. */
|
|
17
|
+
export declare function thrownErrorStatus(error: unknown): {
|
|
18
|
+
requested: number;
|
|
19
|
+
status: number;
|
|
20
|
+
};
|
|
16
21
|
/**
|
|
17
22
|
* Builds the {@linkcode HttpError} that carries one error response's status,
|
|
18
23
|
* title and disclosure.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"error-responder-impl.d.ts","sourceRoot":"","sources":["../../src/middleware/error-responder-impl.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,KAAK,EAEV,iBAAiB,EACjB,eAAe,EAEhB,MAAM,iBAAiB,CAAC;AAGzB,OAAO,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AAEpD,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,kCAAkC,CAAC;
|
|
1
|
+
{"version":3,"file":"error-responder-impl.d.ts","sourceRoot":"","sources":["../../src/middleware/error-responder-impl.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,KAAK,EAEV,iBAAiB,EACjB,eAAe,EAEhB,MAAM,iBAAiB,CAAC;AAGzB,OAAO,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AAEpD,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,kCAAkC,CAAC;AAW9E,gFAAgF;AAChF,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAcvF;AA4BD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,iBAAiB,GAAG,SAAS,CAiCrE;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,oBAAoB,CAClC,SAAS,EAAE,qBAAqB,EAChC,WAAW,EAAE,MAAM,GAClB,eAAe,CA2CjB"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { resolveResponseStatus } from '@setu-ts/common';
|
|
1
|
+
import { httpStatusHintOf, resolveResponseStatus } from '@setu-ts/common';
|
|
2
2
|
import { HttpError } from "../errors/http-error.js";
|
|
3
3
|
import { RESPONDER_DETAIL } from "../formatters/problem-details.js";
|
|
4
4
|
/**
|
|
@@ -9,6 +9,20 @@ import { RESPONDER_DETAIL } from "../formatters/problem-details.js";
|
|
|
9
9
|
* request path) exactly when its resolved content type is this one.
|
|
10
10
|
*/
|
|
11
11
|
const PROBLEM_JSON = 'application/problem+json';
|
|
12
|
+
/** The pure thrown-value rule, shared by serving and request-log prediction. */
|
|
13
|
+
export function thrownErrorStatus(error) {
|
|
14
|
+
const requested = error instanceof HttpError ? error.statusCode : (httpStatusHintOf(error)?.status ?? 500);
|
|
15
|
+
// Fetch refuses informational responses and bodies on 204, 205 and 304.
|
|
16
|
+
const status = Number.isInteger(requested) &&
|
|
17
|
+
requested >= 200 &&
|
|
18
|
+
requested <= 599 &&
|
|
19
|
+
requested !== 204 &&
|
|
20
|
+
requested !== 205 &&
|
|
21
|
+
requested !== 304
|
|
22
|
+
? requested
|
|
23
|
+
: 500;
|
|
24
|
+
return { requested, status };
|
|
25
|
+
}
|
|
12
26
|
/**
|
|
13
27
|
* Narrows an {@linkcode ErrorResponderTarget} to a full {@linkcode
|
|
14
28
|
* IRequestContext} when it genuinely is one.
|
|
@@ -63,9 +77,7 @@ export function buildErrorFromInit(init) {
|
|
|
63
77
|
// through unchanged. Carrying it this way — rather than in `error.message` —
|
|
64
78
|
// keeps the title verbatim, which a site's (non-)disclosure decision depends
|
|
65
79
|
// on.
|
|
66
|
-
const details = init.detail !== undefined
|
|
67
|
-
? { ...init.details, detail: init.detail }
|
|
68
|
-
: init.details;
|
|
80
|
+
const details = init.detail !== undefined ? { ...init.details, detail: init.detail } : init.details;
|
|
69
81
|
const error = new HttpError(init.status, init.title, details);
|
|
70
82
|
// Mark the error as responder-built and carry the disclosure on a
|
|
71
83
|
// module-private symbol, which is what `buildProblemDetails` promotes to the
|
|
@@ -103,6 +115,7 @@ export function buildErrorFromInit(init) {
|
|
|
103
115
|
*/
|
|
104
116
|
export function createErrorResponder(formatter, contentType) {
|
|
105
117
|
return {
|
|
118
|
+
statusFor: (error) => thrownErrorStatus(error).status,
|
|
106
119
|
respond(target, init) {
|
|
107
120
|
// Sanitized before the error is built, not just before the status is
|
|
108
121
|
// written, so the formatted body's `status` member agrees with the status
|
|
@@ -138,10 +151,7 @@ export function createErrorResponder(formatter, contentType) {
|
|
|
138
151
|
body = { ...body, instance: target.request.path };
|
|
139
152
|
}
|
|
140
153
|
const bytes = new TextEncoder().encode(JSON.stringify(body));
|
|
141
|
-
target.response
|
|
142
|
-
.status(status)
|
|
143
|
-
.header('content-type', contentType)
|
|
144
|
-
.send(bytes);
|
|
154
|
+
target.response.status(status).header('content-type', contentType).send(bytes);
|
|
145
155
|
},
|
|
146
156
|
};
|
|
147
157
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"error-responder-impl.js","sourceRoot":"","sources":["../../src/middleware/error-responder-impl.ts"],"names":[],"mappings":"AAkBA,OAAO,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"error-responder-impl.js","sourceRoot":"","sources":["../../src/middleware/error-responder-impl.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@setu-ts/exceptions",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"description": "Exception hierarchy, RFC 9457 Problem Details, and the error-handler middleware",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -18,13 +18,14 @@
|
|
|
18
18
|
"type": "module",
|
|
19
19
|
"license": "MIT",
|
|
20
20
|
"engines": {
|
|
21
|
-
"node": ">=
|
|
21
|
+
"node": "^22.23.4 || ^24.22.1 || >=26.12.1"
|
|
22
22
|
},
|
|
23
23
|
"files": [
|
|
24
24
|
"dist",
|
|
25
25
|
"src",
|
|
26
26
|
"README.md",
|
|
27
|
-
"LICENSE"
|
|
27
|
+
"LICENSE",
|
|
28
|
+
"AGENT_CARD.md"
|
|
28
29
|
],
|
|
29
30
|
"exports": {
|
|
30
31
|
".": {
|
|
@@ -33,18 +34,18 @@
|
|
|
33
34
|
}
|
|
34
35
|
},
|
|
35
36
|
"dependencies": {
|
|
36
|
-
"@setu-ts/common": "^0.
|
|
37
|
+
"@setu-ts/common": "^0.10.0"
|
|
37
38
|
},
|
|
38
39
|
"devDependencies": {
|
|
39
|
-
"@setu-ts/auth-plugin": "^0.
|
|
40
|
-
"@setu-ts/feature-flags-plugin": "^0.
|
|
41
|
-
"@setu-ts/http-security-plugin": "^0.
|
|
42
|
-
"@setu-ts/logger-plugin": "^0.
|
|
43
|
-
"@setu-ts/multi-tenancy-plugin": "^0.
|
|
44
|
-
"@setu-ts/runtime": "^0.
|
|
45
|
-
"@setu-ts/session-plugin": "^0.
|
|
46
|
-
"@setu-ts/storage-plugin": "^0.
|
|
47
|
-
"@setu-ts/testing": "^0.
|
|
40
|
+
"@setu-ts/auth-plugin": "^0.10.0",
|
|
41
|
+
"@setu-ts/feature-flags-plugin": "^0.10.0",
|
|
42
|
+
"@setu-ts/http-security-plugin": "^0.10.0",
|
|
43
|
+
"@setu-ts/logger-plugin": "^0.10.0",
|
|
44
|
+
"@setu-ts/multi-tenancy-plugin": "^0.10.0",
|
|
45
|
+
"@setu-ts/runtime": "^0.10.0",
|
|
46
|
+
"@setu-ts/session-plugin": "^0.10.0",
|
|
47
|
+
"@setu-ts/storage-plugin": "^0.10.0",
|
|
48
|
+
"@setu-ts/testing": "^0.10.0",
|
|
48
49
|
"@types/node": "^24",
|
|
49
50
|
"typescript": "6.0.3",
|
|
50
51
|
"vitest": "5.0.3"
|
package/src/errors/exceptions.ts
CHANGED
|
@@ -151,10 +151,7 @@ export function conflict(message: string): HttpError {
|
|
|
151
151
|
* @returns A `422` {@linkcode HttpError}
|
|
152
152
|
* @since 0.1.0
|
|
153
153
|
*/
|
|
154
|
-
export function validationError(
|
|
155
|
-
errors: readonly ValidationError[],
|
|
156
|
-
message?: string,
|
|
157
|
-
): HttpError {
|
|
154
|
+
export function validationError(errors: readonly ValidationError[], message?: string): HttpError {
|
|
158
155
|
const summary = message ?? `Validation failed with ${errors.length} error(s).`;
|
|
159
156
|
return new HttpError(422, summary, { errors });
|
|
160
157
|
}
|
|
@@ -160,9 +160,10 @@ export function buildProblemDetails(
|
|
|
160
160
|
// that happens to use that key name (M70f code review, finding 4). Only an
|
|
161
161
|
// error the responder built carries the marker, so a thrown error keeps the
|
|
162
162
|
// pre-M70f shape: `detail` is the message.
|
|
163
|
-
const carriedDetail =
|
|
164
|
-
|
|
165
|
-
|
|
163
|
+
const carriedDetail =
|
|
164
|
+
isHttp && RESPONDER_DETAIL in error
|
|
165
|
+
? (error as HttpError & { [RESPONDER_DETAIL]?: unknown })[RESPONDER_DETAIL]
|
|
166
|
+
: undefined;
|
|
166
167
|
const detail = typeof carriedDetail === 'string' ? carriedDetail : error.message;
|
|
167
168
|
|
|
168
169
|
return {
|
|
@@ -20,7 +20,13 @@
|
|
|
20
20
|
*
|
|
21
21
|
* @module
|
|
22
22
|
*/
|
|
23
|
-
import type {
|
|
23
|
+
import type {
|
|
24
|
+
HandlerResult,
|
|
25
|
+
ILogger,
|
|
26
|
+
IRequestContext,
|
|
27
|
+
IRuntimeServices,
|
|
28
|
+
MiddlewareFunction,
|
|
29
|
+
} from '@setu-ts/common';
|
|
24
30
|
import {
|
|
25
31
|
brandErrorResponder,
|
|
26
32
|
CAPABILITIES,
|
|
@@ -32,7 +38,11 @@ import {
|
|
|
32
38
|
|
|
33
39
|
import { HttpError } from '../errors/http-error.ts';
|
|
34
40
|
import { internalServerError, statusTitle } from '../errors/exceptions.ts';
|
|
35
|
-
import {
|
|
41
|
+
import {
|
|
42
|
+
buildErrorFromInit,
|
|
43
|
+
createErrorResponder,
|
|
44
|
+
thrownErrorStatus,
|
|
45
|
+
} from './error-responder-impl.ts';
|
|
36
46
|
import {
|
|
37
47
|
type ErrorFormat,
|
|
38
48
|
type ErrorHandlerFormatter,
|
|
@@ -85,9 +95,8 @@ export interface ErrorHandlerOptions {
|
|
|
85
95
|
*/
|
|
86
96
|
readonly maskInternalErrors?: boolean;
|
|
87
97
|
/**
|
|
88
|
-
* When `true` (the default),
|
|
89
|
-
*
|
|
90
|
-
* registered. When no logger is present, logging is silently skipped.
|
|
98
|
+
* When `true` (the default), logs once at `warn` below 500 and `error`
|
|
99
|
+
* otherwise. Without a logger, only 5xx reaches runtime.reportError.
|
|
91
100
|
*/
|
|
92
101
|
readonly logErrors?: boolean;
|
|
93
102
|
/**
|
|
@@ -178,10 +187,12 @@ export function errorHandler(options?: ErrorHandlerOptions): MiddlewareFunction
|
|
|
178
187
|
} catch (rawError) {
|
|
179
188
|
// Normalize: HttpError passes through, anything else becomes a 500.
|
|
180
189
|
const isHttpError = rawError instanceof HttpError;
|
|
181
|
-
const error: HttpError = isHttpError
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
190
|
+
const error: HttpError = isHttpError
|
|
191
|
+
? rawError
|
|
192
|
+
: internalServerError(
|
|
193
|
+
rawError instanceof Error ? rawError.message : 'Internal Server Error',
|
|
194
|
+
rawError instanceof Error ? rawError : undefined,
|
|
195
|
+
);
|
|
185
196
|
|
|
186
197
|
// A thrower that cannot import this package states the status its own
|
|
187
198
|
// error means by branding it with an `HttpStatusHint` (M89b) — the seam
|
|
@@ -234,7 +245,7 @@ export function errorHandler(options?: ErrorHandlerOptions): MiddlewareFunction
|
|
|
234
245
|
// the write also makes `responseError === error` false, which suppresses
|
|
235
246
|
// the stack — correct by that check's own stated invariant, since the
|
|
236
247
|
// error being SERVED is no longer the error that was LOGGED.
|
|
237
|
-
const servedStatus = resolveResponseStatus(
|
|
248
|
+
const servedStatus = resolveResponseStatus(thrownErrorStatus(rawError).requested, ctx);
|
|
238
249
|
if (servedStatus !== responseError.statusCode) {
|
|
239
250
|
responseError = new HttpError(
|
|
240
251
|
servedStatus,
|
|
@@ -268,8 +279,7 @@ export function errorHandler(options?: ErrorHandlerOptions): MiddlewareFunction
|
|
|
268
279
|
return result;
|
|
269
280
|
}
|
|
270
281
|
|
|
271
|
-
// Log the
|
|
272
|
-
// and the cause chain regardless of masking — and report the status the
|
|
282
|
+
// Log the unmasked diagnostic and cause chain, with the status the
|
|
273
283
|
// formatter below will actually serve. A hint may change a normalized
|
|
274
284
|
// `500` to `501`, so logging the pre-hint status would mislead operators.
|
|
275
285
|
if (logErrors) {
|
|
@@ -315,8 +325,8 @@ export function errorHandler(options?: ErrorHandlerOptions): MiddlewareFunction
|
|
|
315
325
|
}
|
|
316
326
|
|
|
317
327
|
/**
|
|
318
|
-
* Logs
|
|
319
|
-
*
|
|
328
|
+
* Logs once through the registered logger, or the runtime sink for 5xx when
|
|
329
|
+
* no logger is registered. Handled 4xx without a logger stays silent.
|
|
320
330
|
*
|
|
321
331
|
* @param ctx - The request context
|
|
322
332
|
* @param error - The error to log, carrying the unmasked message and cause
|
|
@@ -324,12 +334,22 @@ export function errorHandler(options?: ErrorHandlerOptions): MiddlewareFunction
|
|
|
324
334
|
*/
|
|
325
335
|
function logError(ctx: IRequestContext, error: HttpError, statusCode: number): void {
|
|
326
336
|
if (!ctx.services.has(CAPABILITIES.LOGGER)) {
|
|
337
|
+
if (statusCode >= 500 && ctx.services.has(CAPABILITIES.RUNTIME)) {
|
|
338
|
+
ctx.services.get<IRuntimeServices>(CAPABILITIES.RUNTIME).reportError?.({
|
|
339
|
+
level: 'error',
|
|
340
|
+
...serializeError(error),
|
|
341
|
+
statusCode,
|
|
342
|
+
requestId: ctx.id,
|
|
343
|
+
method: ctx.request.method,
|
|
344
|
+
path: ctx.request.path,
|
|
345
|
+
});
|
|
346
|
+
}
|
|
327
347
|
return;
|
|
328
348
|
}
|
|
329
349
|
const logger = ctx.services.get<ILogger>(CAPABILITIES.LOGGER);
|
|
330
350
|
// Serialize the cause (X2-5): a raw `Error` in log metadata renders as `{}`
|
|
331
351
|
// under `JSON.stringify` because `message`/`stack` are non-enumerable.
|
|
332
|
-
logger
|
|
352
|
+
logger[statusCode < 500 ? 'warn' : 'error'](error.message, {
|
|
333
353
|
statusCode,
|
|
334
354
|
requestId: ctx.id,
|
|
335
355
|
...(error.cause !== undefined && { cause: serializeError(error.cause) }),
|
|
@@ -16,7 +16,7 @@ import type {
|
|
|
16
16
|
IErrorResponder,
|
|
17
17
|
IRequestContext,
|
|
18
18
|
} from '@setu-ts/common';
|
|
19
|
-
import { resolveResponseStatus } from '@setu-ts/common';
|
|
19
|
+
import { httpStatusHintOf, resolveResponseStatus } from '@setu-ts/common';
|
|
20
20
|
|
|
21
21
|
import { HttpError } from '../errors/http-error.ts';
|
|
22
22
|
import { RESPONDER_DETAIL } from '../formatters/problem-details.ts';
|
|
@@ -31,6 +31,23 @@ import type { ErrorHandlerFormatter } from '../formatters/error-formatter.ts';
|
|
|
31
31
|
*/
|
|
32
32
|
const PROBLEM_JSON = 'application/problem+json';
|
|
33
33
|
|
|
34
|
+
/** The pure thrown-value rule, shared by serving and request-log prediction. */
|
|
35
|
+
export function thrownErrorStatus(error: unknown): { requested: number; status: number } {
|
|
36
|
+
const requested =
|
|
37
|
+
error instanceof HttpError ? error.statusCode : (httpStatusHintOf(error)?.status ?? 500);
|
|
38
|
+
// Fetch refuses informational responses and bodies on 204, 205 and 304.
|
|
39
|
+
const status =
|
|
40
|
+
Number.isInteger(requested) &&
|
|
41
|
+
requested >= 200 &&
|
|
42
|
+
requested <= 599 &&
|
|
43
|
+
requested !== 204 &&
|
|
44
|
+
requested !== 205 &&
|
|
45
|
+
requested !== 304
|
|
46
|
+
? requested
|
|
47
|
+
: 500;
|
|
48
|
+
return { requested, status };
|
|
49
|
+
}
|
|
50
|
+
|
|
34
51
|
/**
|
|
35
52
|
* Narrows an {@linkcode ErrorResponderTarget} to a full {@linkcode
|
|
36
53
|
* IRequestContext} when it genuinely is one.
|
|
@@ -88,9 +105,8 @@ export function buildErrorFromInit(init: ErrorResponseInit): HttpError {
|
|
|
88
105
|
// through unchanged. Carrying it this way — rather than in `error.message` —
|
|
89
106
|
// keeps the title verbatim, which a site's (non-)disclosure decision depends
|
|
90
107
|
// on.
|
|
91
|
-
const details =
|
|
92
|
-
? { ...init.details, detail: init.detail }
|
|
93
|
-
: init.details;
|
|
108
|
+
const details =
|
|
109
|
+
init.detail !== undefined ? { ...init.details, detail: init.detail } : init.details;
|
|
94
110
|
const error = new HttpError(init.status, init.title, details);
|
|
95
111
|
// Mark the error as responder-built and carry the disclosure on a
|
|
96
112
|
// module-private symbol, which is what `buildProblemDetails` promotes to the
|
|
@@ -132,6 +148,7 @@ export function createErrorResponder(
|
|
|
132
148
|
contentType: string,
|
|
133
149
|
): IErrorResponder {
|
|
134
150
|
return {
|
|
151
|
+
statusFor: (error: unknown): number => thrownErrorStatus(error).status,
|
|
135
152
|
respond(target: ErrorResponderTarget, init: ErrorResponseInit): void {
|
|
136
153
|
// Sanitized before the error is built, not just before the status is
|
|
137
154
|
// written, so the formatted body's `status` member agrees with the status
|
|
@@ -169,10 +186,7 @@ export function createErrorResponder(
|
|
|
169
186
|
body = { ...body, instance: target.request.path };
|
|
170
187
|
}
|
|
171
188
|
const bytes = new TextEncoder().encode(JSON.stringify(body));
|
|
172
|
-
target.response
|
|
173
|
-
.status(status)
|
|
174
|
-
.header('content-type', contentType)
|
|
175
|
-
.send(bytes);
|
|
189
|
+
target.response.status(status).header('content-type', contentType).send(bytes);
|
|
176
190
|
},
|
|
177
191
|
};
|
|
178
192
|
}
|