@emoyly/problem 8.0.6 → 9.1.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/LICENSE +1 -1
- package/{esm → dist}/problem.d.ts +1 -1
- package/{esm → dist}/util/index.d.ts +1 -0
- package/{esm → dist}/util/index.js +1 -0
- package/dist/util/version.d.ts +2 -0
- package/{esm → dist}/util/version.js +1 -1
- package/package.json +36 -60
- package/{cjs/defaults/4xx.js → src/defaults/4xx.ts} +5 -5
- package/{cjs/defaults/5xx.js → src/defaults/5xx.ts} +5 -5
- package/{cjs/defaults/aws.js → src/defaults/aws.ts} +5 -5
- package/{cjs/defaults/cloudflare.js → src/defaults/cloudflare.ts} +5 -5
- package/{cjs/defaults/iis.js → src/defaults/iis.ts} +5 -5
- package/src/defaults/index.ts +7 -0
- package/{cjs/defaults/nginx.js → src/defaults/nginx.ts} +5 -5
- package/{cjs/defaults/others.js → src/defaults/others.ts} +4 -5
- package/src/index.ts +4 -0
- package/src/middleware/axios.ts +26 -0
- package/src/middleware/base.ts +72 -0
- package/src/middleware/express.ts +52 -0
- package/src/parsers/axios.ts +59 -0
- package/{cjs/parsers/jsonwebtoken.js → src/parsers/jsonwebtoken.ts} +42 -35
- package/src/parsers/mikroorm.ts +22 -0
- package/src/parsers/tsoa.ts +27 -0
- package/src/parsers/zod.ts +23 -0
- package/src/problem.ts +70 -0
- package/src/typings/codes.ts +7 -0
- package/src/typings/index.ts +4 -0
- package/{esm/typings/middleware.d.ts → src/typings/middleware.ts} +8 -5
- package/src/typings/parser.ts +3 -0
- package/{esm/typings/problem.d.ts → src/typings/problem.ts} +18 -17
- package/src/util/getProblems.ts +62 -0
- package/src/util/index.ts +5 -0
- package/src/util/misc.ts +20 -0
- package/{cjs/util/problemArray.js → src/util/problemArray.ts} +11 -11
- package/src/util/validation.ts +39 -0
- package/src/util/version.ts +2 -0
- package/.yarn/versions/524dd037.yml +0 -0
- package/.yarn/versions/b57d536b.yml +0 -0
- package/.yarn/versions/d984e272.yml +0 -0
- package/cjs/defaults/index.js +0 -17
- package/cjs/index.js +0 -22
- package/cjs/middleware/axios.js +0 -23
- package/cjs/middleware/base.js +0 -62
- package/cjs/middleware/express.js +0 -46
- package/cjs/package.json +0 -3
- package/cjs/parsers/axios.js +0 -56
- package/cjs/parsers/mikroorm.js +0 -20
- package/cjs/parsers/tsoa.js +0 -25
- package/cjs/parsers/zod.js +0 -21
- package/cjs/problem.d.ts +0 -14
- package/cjs/problem.js +0 -76
- package/cjs/tsconfig.tsbuildinfo +0 -1
- package/cjs/typings/codes.js +0 -2
- package/cjs/typings/index.js +0 -20
- package/cjs/typings/middleware.js +0 -2
- package/cjs/typings/parser.js +0 -2
- package/cjs/typings/problem.js +0 -12
- package/cjs/util/getProblems.js +0 -62
- package/cjs/util/index.d.ts +0 -4
- package/cjs/util/index.js +0 -20
- package/cjs/util/misc.js +0 -18
- package/cjs/util/validation.js +0 -38
- package/cjs/util/version.d.ts +0 -2
- package/cjs/util/version.js +0 -5
- package/eslint.config.mjs +0 -5
- package/esm/defaults/4xx.d.ts +0 -147
- package/esm/defaults/5xx.d.ts +0 -57
- package/esm/defaults/aws.d.ts +0 -12
- package/esm/defaults/cloudflare.d.ts +0 -47
- package/esm/defaults/iis.d.ts +0 -17
- package/esm/defaults/index.d.ts +0 -7
- package/esm/defaults/nginx.d.ts +0 -32
- package/esm/defaults/others.d.ts +0 -37
- package/esm/index.d.ts +0 -4
- package/esm/middleware/axios.d.ts +0 -13
- package/esm/middleware/base.d.ts +0 -30
- package/esm/middleware/express.d.ts +0 -19
- package/esm/package.json +0 -3
- package/esm/parsers/axios.d.ts +0 -3
- package/esm/parsers/jsonwebtoken.d.ts +0 -3
- package/esm/parsers/mikroorm.d.ts +0 -3
- package/esm/parsers/tsoa.d.ts +0 -3
- package/esm/parsers/zod.d.ts +0 -3
- package/esm/tsconfig.tsbuildinfo +0 -1
- package/esm/typings/codes.d.ts +0 -5
- package/esm/typings/index.d.ts +0 -4
- package/esm/typings/parser.d.ts +0 -2
- package/esm/util/getProblems.d.ts +0 -11
- package/esm/util/misc.d.ts +0 -2
- package/esm/util/problemArray.d.ts +0 -7
- package/esm/util/validation.d.ts +0 -5
- package/esm/util/version.d.ts +0 -2
- package/tsconfig.json +0 -19
- /package/{cjs → dist}/defaults/4xx.d.ts +0 -0
- /package/{esm → dist}/defaults/4xx.js +0 -0
- /package/{cjs → dist}/defaults/5xx.d.ts +0 -0
- /package/{esm → dist}/defaults/5xx.js +0 -0
- /package/{cjs → dist}/defaults/aws.d.ts +0 -0
- /package/{esm → dist}/defaults/aws.js +0 -0
- /package/{cjs → dist}/defaults/cloudflare.d.ts +0 -0
- /package/{esm → dist}/defaults/cloudflare.js +0 -0
- /package/{cjs → dist}/defaults/iis.d.ts +0 -0
- /package/{esm → dist}/defaults/iis.js +0 -0
- /package/{cjs → dist}/defaults/index.d.ts +0 -0
- /package/{esm → dist}/defaults/index.js +0 -0
- /package/{cjs → dist}/defaults/nginx.d.ts +0 -0
- /package/{esm → dist}/defaults/nginx.js +0 -0
- /package/{cjs → dist}/defaults/others.d.ts +0 -0
- /package/{esm → dist}/defaults/others.js +0 -0
- /package/{cjs → dist}/index.d.ts +0 -0
- /package/{esm → dist}/index.js +0 -0
- /package/{cjs → dist}/middleware/axios.d.ts +0 -0
- /package/{esm → dist}/middleware/axios.js +0 -0
- /package/{cjs → dist}/middleware/base.d.ts +0 -0
- /package/{esm → dist}/middleware/base.js +0 -0
- /package/{cjs → dist}/middleware/express.d.ts +0 -0
- /package/{esm → dist}/middleware/express.js +0 -0
- /package/{cjs → dist}/parsers/axios.d.ts +0 -0
- /package/{esm → dist}/parsers/axios.js +0 -0
- /package/{cjs → dist}/parsers/jsonwebtoken.d.ts +0 -0
- /package/{esm → dist}/parsers/jsonwebtoken.js +0 -0
- /package/{cjs → dist}/parsers/mikroorm.d.ts +0 -0
- /package/{esm → dist}/parsers/mikroorm.js +0 -0
- /package/{cjs → dist}/parsers/tsoa.d.ts +0 -0
- /package/{esm → dist}/parsers/tsoa.js +0 -0
- /package/{cjs → dist}/parsers/zod.d.ts +0 -0
- /package/{esm → dist}/parsers/zod.js +0 -0
- /package/{esm → dist}/problem.js +0 -0
- /package/{cjs → dist}/typings/codes.d.ts +0 -0
- /package/{esm → dist}/typings/codes.js +0 -0
- /package/{cjs → dist}/typings/index.d.ts +0 -0
- /package/{esm → dist}/typings/index.js +0 -0
- /package/{cjs → dist}/typings/middleware.d.ts +0 -0
- /package/{esm → dist}/typings/middleware.js +0 -0
- /package/{cjs → dist}/typings/parser.d.ts +0 -0
- /package/{esm → dist}/typings/parser.js +0 -0
- /package/{cjs → dist}/typings/problem.d.ts +0 -0
- /package/{esm → dist}/typings/problem.js +0 -0
- /package/{cjs → dist}/util/getProblems.d.ts +0 -0
- /package/{esm → dist}/util/getProblems.js +0 -0
- /package/{cjs → dist}/util/misc.d.ts +0 -0
- /package/{esm → dist}/util/misc.js +0 -0
- /package/{cjs → dist}/util/problemArray.d.ts +0 -0
- /package/{esm → dist}/util/problemArray.js +0 -0
- /package/{cjs → dist}/util/validation.d.ts +0 -0
- /package/{esm → dist}/util/validation.js +0 -0
package/LICENSE
CHANGED
|
@@ -6,7 +6,7 @@ export declare class Problem extends Error implements IProblem {
|
|
|
6
6
|
detail: string | undefined;
|
|
7
7
|
instance: string | undefined;
|
|
8
8
|
errorObject?: unknown;
|
|
9
|
-
readonly __problemVersion = "
|
|
9
|
+
readonly __problemVersion = "9.1.0";
|
|
10
10
|
data: unknown;
|
|
11
11
|
source?: string;
|
|
12
12
|
constructor(opts: ProblemOpts);
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export const version = '
|
|
1
|
+
export const version = '9.1.0';
|
|
2
2
|
export const major = Number(version.split('.')[0]);
|
package/package.json
CHANGED
|
@@ -1,39 +1,41 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@emoyly/problem",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "9.1.0",
|
|
4
4
|
"description": "A simple error library based around the RFC-7807 standard with optional support for different parsers and middleware.",
|
|
5
|
-
"main": "
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
6
7
|
"repository": "https://github.com/emoyly/problem",
|
|
7
8
|
"author": "Emil Petersen <emoyly@gmail.com>",
|
|
8
9
|
"license": "MIT",
|
|
9
10
|
"type": "module",
|
|
10
11
|
"scripts": {
|
|
11
|
-
"build": "
|
|
12
|
-
"lint": "eslint src
|
|
12
|
+
"build": "concurrently \"tsc\" \"tsc -p test/tsconfig.json\"",
|
|
13
|
+
"lint": "concurrently \"eslint src\" \"eslint test\"",
|
|
13
14
|
"publish": "yarn npm publish --access public --tolerate-republish",
|
|
14
|
-
"test": "node --import @swc-node/register/esm-register --test ./test/**/*.test.ts"
|
|
15
|
+
"test": "concurrently \"node --import @swc-node/register/esm-register --test ./test/**/*.test.ts\" \"node --import @swc-node/register/esm-register ./test/consumptionTest.ts\""
|
|
15
16
|
},
|
|
16
17
|
"devDependencies": {
|
|
17
|
-
"@emoyly/devutils": "^1.0
|
|
18
|
-
"@emoyly/eslint-config": "^1.
|
|
19
|
-
"@emoyly/utils": "^
|
|
20
|
-
"@mikro-orm/core": "^6.
|
|
21
|
-
"@stylistic/eslint-plugin-js": "^
|
|
22
|
-
"@stylistic/eslint-plugin-ts": "^
|
|
23
|
-
"@swc-node/register": "^1.
|
|
24
|
-
"@swc/core": "^1.
|
|
25
|
-
"@types/express": "^5.0.
|
|
26
|
-
"@types/jsonwebtoken": "^9.0.
|
|
27
|
-
"@types/node": "^
|
|
28
|
-
"@types/semver": "^7.
|
|
29
|
-
"axios": "^1.
|
|
30
|
-
"
|
|
18
|
+
"@emoyly/devutils": "^1.1.0",
|
|
19
|
+
"@emoyly/eslint-config": "^1.2.2",
|
|
20
|
+
"@emoyly/utils": "^2.0.3",
|
|
21
|
+
"@mikro-orm/core": "^6.5.3",
|
|
22
|
+
"@stylistic/eslint-plugin-js": "^4.4.1",
|
|
23
|
+
"@stylistic/eslint-plugin-ts": "^4.4.1",
|
|
24
|
+
"@swc-node/register": "^1.11.1",
|
|
25
|
+
"@swc/core": "^1.13.5",
|
|
26
|
+
"@types/express": "^5.0.3",
|
|
27
|
+
"@types/jsonwebtoken": "^9.0.10",
|
|
28
|
+
"@types/node": "^24.5.0",
|
|
29
|
+
"@types/semver": "^7.7.1",
|
|
30
|
+
"axios": "^1.12.2",
|
|
31
|
+
"concurrently": "^9.2.1",
|
|
32
|
+
"eslint": "^9.35.0",
|
|
31
33
|
"jsonwebtoken": "^9.0.2",
|
|
32
|
-
"semver": "^7.
|
|
34
|
+
"semver": "^7.7.2",
|
|
33
35
|
"tsoa": "^6.6.0",
|
|
34
|
-
"typescript": "^5.
|
|
35
|
-
"typescript-eslint": "^8.
|
|
36
|
-
"zod": "^
|
|
36
|
+
"typescript": "^5.9.2",
|
|
37
|
+
"typescript-eslint": "^8.44.0",
|
|
38
|
+
"zod": "^4.1.8"
|
|
37
39
|
},
|
|
38
40
|
"peerDependencies": {
|
|
39
41
|
"@mikro-orm/core": "*",
|
|
@@ -59,47 +61,21 @@
|
|
|
59
61
|
"optional": true
|
|
60
62
|
}
|
|
61
63
|
},
|
|
62
|
-
"packageManager": "yarn@4.
|
|
64
|
+
"packageManager": "yarn@4.9.4",
|
|
63
65
|
"publishConfig": {
|
|
64
66
|
"access": "public",
|
|
65
67
|
"registry": "https://registry.npmjs.org"
|
|
66
68
|
},
|
|
67
69
|
"exports": {
|
|
68
|
-
".":
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
"./middleware/
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
"./
|
|
77
|
-
|
|
78
|
-
"require": "./cjs/middleware/base.js"
|
|
79
|
-
},
|
|
80
|
-
"./middleware/express": {
|
|
81
|
-
"import": "./esm/middleware/express.js",
|
|
82
|
-
"require": "./cjs/middleware/express.js"
|
|
83
|
-
},
|
|
84
|
-
"./parsers/axios": {
|
|
85
|
-
"import": "./esm/parsers/axios.js",
|
|
86
|
-
"require": "./cjs/parsers/axios.js"
|
|
87
|
-
},
|
|
88
|
-
"./parsers/jsonwebtoken": {
|
|
89
|
-
"import": "./esm/parsers/jsonwebtoken.js",
|
|
90
|
-
"require": "./cjs/parsers/jsonwebtoken.js"
|
|
91
|
-
},
|
|
92
|
-
"./parsers/mikroorm": {
|
|
93
|
-
"import": "./esm/parsers/mikroorm.js",
|
|
94
|
-
"require": "./cjs/parsers/mikroorm.js"
|
|
95
|
-
},
|
|
96
|
-
"./parsers/tsoa": {
|
|
97
|
-
"import": "./esm/parsers/tsoa.js",
|
|
98
|
-
"require": "./cjs/parsers/tsoa.js"
|
|
99
|
-
},
|
|
100
|
-
"./parsers/zod": {
|
|
101
|
-
"import": "./esm/parsers/zod.js",
|
|
102
|
-
"require": "./cjs/parsers/zod.js"
|
|
103
|
-
}
|
|
70
|
+
".": "./dist/index.js",
|
|
71
|
+
"./defaults": "./dist/defaults/index.js",
|
|
72
|
+
"./middleware/axios": "./dist/middleware/axios.js",
|
|
73
|
+
"./middleware/base": "./dist/middleware/base.js",
|
|
74
|
+
"./middleware/express": "./dist/middleware/express.js",
|
|
75
|
+
"./parsers/axios": "./dist/parsers/axios.js",
|
|
76
|
+
"./parsers/jsonwebtoken": "./dist/parsers/jsonwebtoken.js",
|
|
77
|
+
"./parsers/mikroorm": "./dist/parsers/mikroorm.js",
|
|
78
|
+
"./parsers/tsoa": "./dist/parsers/tsoa.js",
|
|
79
|
+
"./parsers/zod": "./dist/parsers/zod.js"
|
|
104
80
|
}
|
|
105
81
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
"use strict";
|
|
2
1
|
// Source: https://en.wikipedia.org/wiki/List_of_HTTP_status_codes
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
|
|
3
|
+
import { Codes } from '../typings/codes.js'
|
|
4
|
+
|
|
5
|
+
export const statusCodes = {
|
|
6
6
|
400: {
|
|
7
7
|
status: 400,
|
|
8
8
|
title: 'Bad Request',
|
|
@@ -148,4 +148,4 @@ exports.statusCodes = {
|
|
|
148
148
|
title: 'Unavailable For Legal Reasons (RFC 7725)',
|
|
149
149
|
type: 'errors/defaults/4xx/unavailableforlegalreasons'
|
|
150
150
|
}
|
|
151
|
-
}
|
|
151
|
+
} as const satisfies Codes
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
"use strict";
|
|
2
1
|
// Source: https://en.wikipedia.org/wiki/List_of_HTTP_status_codes
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
|
|
3
|
+
import { Codes } from '../typings/codes.js'
|
|
4
|
+
|
|
5
|
+
export const statusCodes = {
|
|
6
6
|
500: {
|
|
7
7
|
status: 500,
|
|
8
8
|
title: 'Internal Server Error',
|
|
@@ -58,4 +58,4 @@ exports.statusCodes = {
|
|
|
58
58
|
title: 'Not Extended (RFC 2774)',
|
|
59
59
|
type: 'errors/defaults/5xx/notextended'
|
|
60
60
|
}
|
|
61
|
-
}
|
|
61
|
+
} as const satisfies Codes
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
"use strict";
|
|
2
1
|
// Source: https://docs.aws.amazon.com/elasticloadbalancing/latest/application/load-balancer-troubleshooting.html
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
|
|
3
|
+
import { Codes } from '../typings/codes.js'
|
|
4
|
+
|
|
5
|
+
export const statusCodes = {
|
|
6
6
|
460: {
|
|
7
7
|
status: 460,
|
|
8
8
|
title: 'Client closed connection before idle timeout period elapsed',
|
|
@@ -13,4 +13,4 @@ exports.statusCodes = {
|
|
|
13
13
|
title: 'X-Forwarded-For contains more than 30 ips',
|
|
14
14
|
type: '/errors/defaults/aws/toomanyips'
|
|
15
15
|
}
|
|
16
|
-
}
|
|
16
|
+
} as const satisfies Codes
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
"use strict";
|
|
2
1
|
// Source: https://support.cloudflare.com/hc/en-us/articles/115003011431
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
|
|
3
|
+
import { Codes } from '../typings/codes.js'
|
|
4
|
+
|
|
5
|
+
export const statusCodes = {
|
|
6
6
|
520: {
|
|
7
7
|
status: 520,
|
|
8
8
|
title: 'Web Server returned an unknown error',
|
|
@@ -49,4 +49,4 @@ exports.statusCodes = {
|
|
|
49
49
|
title: 'Cloudflare returned 1xxx error',
|
|
50
50
|
type: '/errors/defaults/cloudflare/1xxx'
|
|
51
51
|
}
|
|
52
|
-
}
|
|
52
|
+
} as const satisfies Codes
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
"use strict";
|
|
2
1
|
// Source: https://en.wikipedia.org/wiki/List_of_HTTP_status_codes
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
|
|
3
|
+
import { Codes } from '../typings/codes.js'
|
|
4
|
+
|
|
5
|
+
export const statusCodes = {
|
|
6
6
|
440: {
|
|
7
7
|
status: 440,
|
|
8
8
|
title: 'Login Time-out',
|
|
@@ -18,4 +18,4 @@ exports.statusCodes = {
|
|
|
18
18
|
title: 'Redirect',
|
|
19
19
|
type: '/errors/defaults/iis/redirect'
|
|
20
20
|
}
|
|
21
|
-
}
|
|
21
|
+
} as const satisfies Codes
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { statusCodes as codes4xx } from '../defaults/4xx.js'
|
|
2
|
+
export { statusCodes as codes5xx } from '../defaults/5xx.js'
|
|
3
|
+
export { statusCodes as codesAws } from '../defaults/aws.js'
|
|
4
|
+
export { statusCodes as codesCloudflare } from '../defaults/cloudflare.js'
|
|
5
|
+
export { statusCodes as codesIis } from '../defaults/iis.js'
|
|
6
|
+
export { statusCodes as codesNginx } from '../defaults/nginx.js'
|
|
7
|
+
export { otherErrors } from '../defaults/others.js'
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
"use strict";
|
|
2
1
|
// Source: https://en.wikipedia.org/wiki/List_of_HTTP_status_codes
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
|
|
3
|
+
import { Codes } from '../typings/codes.js'
|
|
4
|
+
|
|
5
|
+
export const statusCodes = {
|
|
6
6
|
444: {
|
|
7
7
|
status: 494,
|
|
8
8
|
title: 'No Response',
|
|
@@ -33,4 +33,4 @@ exports.statusCodes = {
|
|
|
33
33
|
title: 'Client Closed Request',
|
|
34
34
|
type: '/errors/defaults/nginx/clientclosedreq'
|
|
35
35
|
}
|
|
36
|
-
}
|
|
36
|
+
} as const satisfies Codes
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
exports.otherErrors = {
|
|
1
|
+
import { Codes } from '../typings/codes.js'
|
|
2
|
+
|
|
3
|
+
export const otherErrors = {
|
|
5
4
|
'unknown': {
|
|
6
5
|
status: 500,
|
|
7
6
|
title: 'Unknown error',
|
|
@@ -37,4 +36,4 @@ exports.otherErrors = {
|
|
|
37
36
|
title: 'Input failed validation',
|
|
38
37
|
type: '/errors/defaults/others/validationerror'
|
|
39
38
|
}
|
|
40
|
-
}
|
|
39
|
+
} as const satisfies Codes
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { PartialMiddlewareOptions } from '../typings/middleware.js'
|
|
2
|
+
import { MiddlewareBase } from './base.js'
|
|
3
|
+
import axiosParser from '../parsers/axios.js'
|
|
4
|
+
|
|
5
|
+
type InterceptorArray = [undefined, (error: unknown) => Promise<never>]
|
|
6
|
+
|
|
7
|
+
export class AxiosMiddleware extends MiddlewareBase {
|
|
8
|
+
readonly name = 'axios'
|
|
9
|
+
|
|
10
|
+
constructor(options?: PartialMiddlewareOptions) {
|
|
11
|
+
super({
|
|
12
|
+
'parsers': [axiosParser]
|
|
13
|
+
}, options)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
private readonly interceptor = async (error: unknown) => {
|
|
17
|
+
const problems = this.parse(error)
|
|
18
|
+
this.emit(problems)
|
|
19
|
+
return Promise.reject(problems)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @example instance.interceptors.response.use(...middleware.use())
|
|
24
|
+
*/
|
|
25
|
+
readonly use = (): InterceptorArray => [undefined, this.interceptor]
|
|
26
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { otherErrors } from '../defaults/index.js'
|
|
2
|
+
import { Problem } from '../problem.js'
|
|
3
|
+
import type { ProblemOpts } from '../typings/index.js'
|
|
4
|
+
import type { MiddlewareOptions, PartialMiddlewareOptions, ProblemListener } from '../typings/middleware.js'
|
|
5
|
+
import { getProblems } from '../util/getProblems.js'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Middleware collects errors from somewhere, transforms them into something recognizable by a parser, and then passes them to the parsers, and then returns an array of Problems to whatever is using the middleware
|
|
9
|
+
*/
|
|
10
|
+
export abstract class MiddlewareBase {
|
|
11
|
+
readonly options: MiddlewareOptions
|
|
12
|
+
abstract readonly name: string
|
|
13
|
+
/**
|
|
14
|
+
* When nothing is returned from the parsers, just throw in a default "fallback" Problem object, so that a Problem is still actually returned/emitted
|
|
15
|
+
*/
|
|
16
|
+
readonly enableFallback: boolean = true
|
|
17
|
+
|
|
18
|
+
constructor(defaultOptions: MiddlewareOptions, options?: PartialMiddlewareOptions) {
|
|
19
|
+
this.options = { ...defaultOptions, ...options }
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Parse input using parsers
|
|
24
|
+
*/
|
|
25
|
+
protected readonly parse = (input: unknown): Problem[] => {
|
|
26
|
+
const problems = getProblems(input, this.options.parsers)
|
|
27
|
+
|
|
28
|
+
if (!problems || !Array.isArray(problems) || problems.length < 1) {
|
|
29
|
+
if (this.enableFallback) return [new Problem({
|
|
30
|
+
...this.fallback,
|
|
31
|
+
'errorObject': input,
|
|
32
|
+
'source': `middleware:${this.name}`
|
|
33
|
+
})]
|
|
34
|
+
|
|
35
|
+
return []
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
for (const p of problems) {
|
|
39
|
+
if (!p.source) p.source = `middleware:${this.name}`
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return problems
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
protected readonly fallback: ProblemOpts = otherErrors.unknown
|
|
46
|
+
|
|
47
|
+
protected readonly problemListeners: ProblemListener[] = []
|
|
48
|
+
|
|
49
|
+
protected emit = (list: Problem[]) => {
|
|
50
|
+
for (const listener of this.problemListeners) {
|
|
51
|
+
listener(list)
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Listen to problems
|
|
57
|
+
*/
|
|
58
|
+
readonly onProblem = (listener: ProblemListener) => {
|
|
59
|
+
this.problemListeners.push(listener)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Un-listen to problems
|
|
64
|
+
*/
|
|
65
|
+
readonly offProblem = (listener: ProblemListener) => {
|
|
66
|
+
const idx = this.problemListeners.findIndex(val => val === listener)
|
|
67
|
+
|
|
68
|
+
if (idx < 0 || isNaN(idx)) return
|
|
69
|
+
|
|
70
|
+
this.problemListeners.splice(idx, 1)
|
|
71
|
+
}
|
|
72
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { NextFunction, Request, Response } from 'express'
|
|
2
|
+
import type { PartialMiddlewareOptions } from '../typings/middleware.js'
|
|
3
|
+
import { MiddlewareBase } from './base.js'
|
|
4
|
+
import { Problem } from '../problem.js'
|
|
5
|
+
import { codes4xx } from '../defaults/index.js'
|
|
6
|
+
|
|
7
|
+
export class ExpressMiddleware extends MiddlewareBase {
|
|
8
|
+
readonly name = 'express'
|
|
9
|
+
|
|
10
|
+
private middleware = (error: unknown, req: Request, res: Response, next: NextFunction) => {
|
|
11
|
+
const problems = this.parse(error)
|
|
12
|
+
for (const problem of problems) {
|
|
13
|
+
if (!problem.instance) {
|
|
14
|
+
problem.instance = req.originalUrl
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
this.emit(problems)
|
|
19
|
+
|
|
20
|
+
res
|
|
21
|
+
.contentType('application/problem+json')
|
|
22
|
+
.status(problems[0]?.status ?? 500)
|
|
23
|
+
.json(problems.map(val => val.toObject()))
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* This function applies the middleware to your Express application. Put this at the very end of your middleware/routes.
|
|
28
|
+
* If you want to also handle 404 errors, check out the notFound middleware function.
|
|
29
|
+
* @example app.use(middleware.use())
|
|
30
|
+
*/
|
|
31
|
+
readonly use = () => this.middleware
|
|
32
|
+
|
|
33
|
+
constructor(options?: PartialMiddlewareOptions) {
|
|
34
|
+
super({
|
|
35
|
+
'parsers': []
|
|
36
|
+
}, options)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Put this right before the middleware.use() to catch all 404 errors and respond with a Problem instead of the default Express response
|
|
41
|
+
* @example app.use(middleware.notFound)
|
|
42
|
+
*/
|
|
43
|
+
readonly notFound = (req: Request, res: Response, next: NextFunction) => {
|
|
44
|
+
const error = new Problem({
|
|
45
|
+
...codes4xx[404],
|
|
46
|
+
'instance': req.originalUrl,
|
|
47
|
+
'detail': `No API endpoints exist on ${req.url} for request method ${req.method}`
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
return next(error)
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { otherErrors } from '../defaults/others.js'
|
|
2
|
+
import { Problem } from '../problem.js'
|
|
3
|
+
import type { Parser } from '../typings/parser.js'
|
|
4
|
+
import { getProblems } from '../util/getProblems.js'
|
|
5
|
+
import { getCodeDefaults } from '../util/misc.js'
|
|
6
|
+
import { isAxiosError } from 'axios'
|
|
7
|
+
|
|
8
|
+
const parse: Parser = (input) => {
|
|
9
|
+
if (typeof input !== 'object' || input == null) return []
|
|
10
|
+
if (!isAxiosError(input)) return []
|
|
11
|
+
const request = input.request
|
|
12
|
+
|
|
13
|
+
if (input?.response?.data) {
|
|
14
|
+
const problems = getProblems(input?.response?.data)
|
|
15
|
+
if (problems?.length) return problems
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (input?.response) {
|
|
19
|
+
const opts = getCodeDefaults(input.response.status)
|
|
20
|
+
if (opts) return [new Problem({
|
|
21
|
+
...opts,
|
|
22
|
+
'instance': input?.config?.url,
|
|
23
|
+
'stack': input?.stack,
|
|
24
|
+
'errorObject': input,
|
|
25
|
+
'source': 'parser:axios'
|
|
26
|
+
})]
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// TODO: Fix this, this seems bad
|
|
30
|
+
if (request) {
|
|
31
|
+
const isNode = typeof process !== 'undefined' && process.versions && process.versions.node
|
|
32
|
+
const isBrowser = typeof window !== 'undefined' && typeof window.document !== 'undefined'
|
|
33
|
+
|
|
34
|
+
if (isBrowser && request instanceof XMLHttpRequest) {
|
|
35
|
+
return [new Problem({
|
|
36
|
+
...otherErrors.networkError,
|
|
37
|
+
'instance': input?.config?.url,
|
|
38
|
+
'stack': input?.stack,
|
|
39
|
+
'errorObject': input,
|
|
40
|
+
'source': 'parser:axios'
|
|
41
|
+
})]
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Probably a ClientRequest
|
|
45
|
+
if (isNode && 'pipe' in request && 'destroy' in request) {
|
|
46
|
+
return [new Problem({
|
|
47
|
+
...otherErrors.networkError,
|
|
48
|
+
'instance': input?.config?.url,
|
|
49
|
+
'stack': input?.stack,
|
|
50
|
+
'errorObject': input,
|
|
51
|
+
'source': 'parser:axios'
|
|
52
|
+
})]
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return []
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export default parse
|
|
@@ -1,8 +1,14 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
import { Problem } from '../problem.js'
|
|
2
|
+
import type { Parser } from '../typings/parser.js'
|
|
3
|
+
import { JsonWebTokenError, NotBeforeError, TokenExpiredError } from 'jsonwebtoken'
|
|
4
|
+
import type { ProblemOpts } from '../typings/problem.js'
|
|
5
|
+
|
|
6
|
+
interface ErrorMap {
|
|
7
|
+
'search': string | RegExp,
|
|
8
|
+
'result': ProblemOpts,
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const errorMap: ErrorMap[] = [
|
|
6
12
|
{
|
|
7
13
|
'search': 'invalid token',
|
|
8
14
|
'result': {
|
|
@@ -67,37 +73,38 @@ const errorMap = [
|
|
|
67
73
|
'title': 'The JSON Web Token subject is invalid',
|
|
68
74
|
}
|
|
69
75
|
}
|
|
70
|
-
]
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
76
|
+
]
|
|
77
|
+
|
|
78
|
+
const parse: Parser = (input) => {
|
|
79
|
+
if (input instanceof JsonWebTokenError && input.name === 'JsonWebTokenError') {
|
|
80
|
+
const found = errorMap.find(val => typeof val.search === 'string' ? val.search === input.message : val.search.test(input.message))
|
|
81
|
+
|
|
74
82
|
if (found) {
|
|
75
|
-
return [new
|
|
76
|
-
|
|
77
|
-
'errorObject': input,
|
|
78
|
-
'source': 'parser:jsonwebtoken'
|
|
79
|
-
})];
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
else if (input instanceof jsonwebtoken_1.TokenExpiredError && input.name === 'TokenExpiredError') {
|
|
83
|
-
return [new problem_js_1.Problem({
|
|
84
|
-
'title': 'The JSON Web Token has expired',
|
|
85
|
-
'type': '/errors/jsonwebtoken/tokenexpired',
|
|
86
|
-
'detail': `The JSON Web Token expired at ${input.expiredAt}`,
|
|
87
|
-
'status': 400,
|
|
88
|
-
'errorObject': input,
|
|
89
|
-
'source': 'parser:jsonwebtoken'
|
|
90
|
-
})];
|
|
91
|
-
}
|
|
92
|
-
else if (input instanceof jsonwebtoken_1.NotBeforeError && input.name === 'NotBeforeError') {
|
|
93
|
-
return [new problem_js_1.Problem({
|
|
94
|
-
'title': 'The JSON Web Token is not valid yet',
|
|
95
|
-
'type': '/errors/jsonwebtoken/notbefore',
|
|
96
|
-
'detail': `The JSON Web Token is not valid before ${input.date}`,
|
|
97
|
-
'status': 400,
|
|
83
|
+
return [new Problem({
|
|
84
|
+
...found.result,
|
|
98
85
|
'errorObject': input,
|
|
99
86
|
'source': 'parser:jsonwebtoken'
|
|
100
|
-
})]
|
|
87
|
+
})]
|
|
88
|
+
}
|
|
89
|
+
} else if (input instanceof TokenExpiredError && input.name === 'TokenExpiredError') {
|
|
90
|
+
return [new Problem({
|
|
91
|
+
'title': 'The JSON Web Token has expired',
|
|
92
|
+
'type': '/errors/jsonwebtoken/tokenexpired',
|
|
93
|
+
'detail': `The JSON Web Token expired at ${input.expiredAt}`,
|
|
94
|
+
'status': 400,
|
|
95
|
+
'errorObject': input,
|
|
96
|
+
'source': 'parser:jsonwebtoken'
|
|
97
|
+
})]
|
|
98
|
+
} else if (input instanceof NotBeforeError && input.name === 'NotBeforeError') {
|
|
99
|
+
return [new Problem({
|
|
100
|
+
'title': 'The JSON Web Token is not valid yet',
|
|
101
|
+
'type': '/errors/jsonwebtoken/notbefore',
|
|
102
|
+
'detail': `The JSON Web Token is not valid before ${input.date}`,
|
|
103
|
+
'status': 400,
|
|
104
|
+
'errorObject': input,
|
|
105
|
+
'source': 'parser:jsonwebtoken'
|
|
106
|
+
})]
|
|
101
107
|
}
|
|
102
|
-
}
|
|
103
|
-
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export default parse
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { Problem } from '../problem.js'
|
|
2
|
+
import { statusCodes } from '../defaults/4xx.js'
|
|
3
|
+
import type { Parser } from '../typings/parser.js'
|
|
4
|
+
import { NotFoundError } from '@mikro-orm/core'
|
|
5
|
+
|
|
6
|
+
// TODO: Make this less bad
|
|
7
|
+
const parse: Parser = (input) => {
|
|
8
|
+
if (!(input instanceof NotFoundError)) {
|
|
9
|
+
return
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
return [
|
|
13
|
+
new Problem({
|
|
14
|
+
...statusCodes['404'],
|
|
15
|
+
'stack': input.stack,
|
|
16
|
+
'errorObject': input,
|
|
17
|
+
'source': 'parser:mikroorm'
|
|
18
|
+
})
|
|
19
|
+
]
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export default parse
|