@emoyly/problem 7.0.6 → 8.0.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/README.md +1 -1
- package/cjs/defaults/4xx.d.ts +144 -144
- package/cjs/defaults/5xx.d.ts +54 -54
- package/cjs/defaults/aws.d.ts +8 -8
- package/cjs/defaults/cloudflare.d.ts +44 -44
- package/cjs/defaults/iis.d.ts +12 -12
- package/cjs/defaults/nginx.d.ts +24 -24
- package/cjs/defaults/others.d.ts +28 -28
- package/cjs/middleware/axios.js +1 -1
- package/cjs/middleware/base.d.ts +1 -1
- package/cjs/middleware/base.js +4 -4
- package/cjs/middleware/express.js +10 -28
- package/cjs/parsers/axios.js +9 -8
- package/cjs/parsers/jsonwebtoken.js +3 -0
- package/cjs/parsers/mikroorm.js +1 -0
- package/cjs/parsers/tsoa.js +3 -2
- package/cjs/parsers/zod.js +2 -1
- package/cjs/problem.d.ts +7 -7
- package/cjs/problem.js +41 -32
- package/cjs/tsconfig.tsbuildinfo +1 -1
- package/cjs/typings/codes.d.ts +2 -2
- package/cjs/typings/problem.d.ts +13 -2
- package/cjs/util/getProblems.d.ts +4 -4
- package/cjs/util/getProblems.js +18 -15
- package/cjs/util/index.d.ts +1 -1
- package/cjs/util/index.js +1 -1
- package/cjs/util/misc.d.ts +2 -2
- package/cjs/util/misc.js +3 -4
- package/cjs/util/validation.d.ts +5 -0
- package/cjs/util/validation.js +38 -0
- package/cjs/util/version.d.ts +1 -2
- package/cjs/util/version.js +1 -16
- package/esm/defaults/4xx.d.ts +144 -144
- package/esm/defaults/5xx.d.ts +54 -54
- package/esm/defaults/aws.d.ts +8 -8
- package/esm/defaults/cloudflare.d.ts +44 -44
- package/esm/defaults/iis.d.ts +12 -12
- package/esm/defaults/nginx.d.ts +24 -24
- package/esm/defaults/others.d.ts +28 -28
- package/esm/middleware/axios.js +1 -1
- package/esm/middleware/base.d.ts +1 -1
- package/esm/middleware/base.js +4 -4
- package/esm/middleware/express.js +11 -29
- package/esm/parsers/axios.js +9 -8
- package/esm/parsers/jsonwebtoken.js +3 -0
- package/esm/parsers/mikroorm.js +1 -0
- package/esm/parsers/tsoa.js +3 -2
- package/esm/parsers/zod.js +2 -1
- package/esm/problem.d.ts +7 -7
- package/esm/problem.js +41 -32
- package/esm/tsconfig.tsbuildinfo +1 -1
- package/esm/typings/codes.d.ts +2 -2
- package/esm/typings/problem.d.ts +13 -2
- package/esm/util/getProblems.d.ts +4 -4
- package/esm/util/getProblems.js +18 -15
- package/esm/util/index.d.ts +1 -1
- package/esm/util/index.js +1 -1
- package/esm/util/misc.d.ts +2 -2
- package/esm/util/misc.js +2 -3
- package/esm/util/validation.d.ts +5 -0
- package/esm/util/validation.js +33 -0
- package/esm/util/version.d.ts +1 -2
- package/esm/util/version.js +1 -15
- package/package.json +18 -13
- package/tsconfig.json +5 -1
- package/.editorconfig +0 -11
- package/.vscode/extensions.json +0 -7
- package/.vscode/settings.json +0 -2
- package/cjs/util/defaults.d.ts +0 -4
- package/cjs/util/defaults.js +0 -7
- package/esm/util/defaults.d.ts +0 -4
- package/esm/util/defaults.js +0 -4
- package/scripts/ensureCorrectVersion.js +0 -20
- package/src/defaults/4xx.ts +0 -149
- package/src/defaults/5xx.ts +0 -59
- package/src/defaults/aws.ts +0 -14
- package/src/defaults/cloudflare.ts +0 -50
- package/src/defaults/iis.ts +0 -19
- package/src/defaults/index.ts +0 -7
- package/src/defaults/nginx.ts +0 -34
- package/src/defaults/others.ts +0 -37
- package/src/index.ts +0 -4
- package/src/middleware/axios.ts +0 -28
- package/src/middleware/base.ts +0 -78
- package/src/middleware/express.ts +0 -71
- package/src/parsers/axios.ts +0 -60
- package/src/parsers/jsonwebtoken.ts +0 -107
- package/src/parsers/mikroorm.ts +0 -21
- package/src/parsers/tsoa.ts +0 -26
- package/src/parsers/zod.ts +0 -23
- package/src/problem.ts +0 -56
- package/src/typings/codes.ts +0 -6
- package/src/typings/index.ts +0 -4
- package/src/typings/middleware.ts +0 -14
- package/src/typings/parser.ts +0 -3
- package/src/typings/problem.ts +0 -27
- package/src/util/defaults.ts +0 -4
- package/src/util/getProblems.ts +0 -56
- package/src/util/index.ts +0 -4
- package/src/util/misc.ts +0 -21
- package/src/util/problemArray.ts +0 -21
- package/src/util/version.ts +0 -16
package/src/index.ts
DELETED
package/src/middleware/axios.ts
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
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
|
-
name = 'axios'
|
|
9
|
-
|
|
10
|
-
constructor(options?: PartialMiddlewareOptions) {
|
|
11
|
-
super({
|
|
12
|
-
'parsers': [axiosParser]
|
|
13
|
-
}, options)
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
interceptor = async (error: unknown) => {
|
|
17
|
-
const problems = await this.parse(error)
|
|
18
|
-
|
|
19
|
-
this.emit(problems)
|
|
20
|
-
|
|
21
|
-
return Promise.reject(problems)
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* @example instance.interceptors.response.use(...middleware.use())
|
|
26
|
-
*/
|
|
27
|
-
use = (): InterceptorArray => [undefined, this.interceptor]
|
|
28
|
-
}
|
package/src/middleware/base.ts
DELETED
|
@@ -1,78 +0,0 @@
|
|
|
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
|
-
options: MiddlewareOptions
|
|
12
|
-
abstract 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
|
-
enableFallback = true
|
|
17
|
-
|
|
18
|
-
constructor(defaultOptions: MiddlewareOptions, options?: PartialMiddlewareOptions) {
|
|
19
|
-
this.options = { ...defaultOptions, ...options }
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Call this function when you want to use the middleware function
|
|
24
|
-
*/
|
|
25
|
-
abstract use(): unknown
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Parse input using parsers
|
|
29
|
-
*/
|
|
30
|
-
parse = async (input: unknown): Promise<Problem[]> => {
|
|
31
|
-
const problems = getProblems(input, this.options.parsers)
|
|
32
|
-
|
|
33
|
-
if (!problems || !Array.isArray(problems) || problems.length < 1) {
|
|
34
|
-
if (this.enableFallback) return [new Problem({
|
|
35
|
-
...this.fallback,
|
|
36
|
-
'errorObject': input,
|
|
37
|
-
'stack': (typeof input === 'object' && input !== null && 'stack' in input && typeof input.stack === 'string') ? input.stack : undefined,
|
|
38
|
-
'middleware': this.name
|
|
39
|
-
})]
|
|
40
|
-
|
|
41
|
-
return []
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
for (const p of problems) {
|
|
45
|
-
if (!p.middleware) p.middleware = this.name
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
return problems
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
fallback: ProblemOpts = otherErrors.unknown
|
|
52
|
-
|
|
53
|
-
problemListeners: ProblemListener[] = []
|
|
54
|
-
|
|
55
|
-
protected emit = (list: Problem[]) => {
|
|
56
|
-
for (const listener of this.problemListeners) {
|
|
57
|
-
listener(list)
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Listen to problems
|
|
63
|
-
*/
|
|
64
|
-
onProblem = (listener: ProblemListener) => {
|
|
65
|
-
this.problemListeners.push(listener)
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Un-listen to problems
|
|
70
|
-
*/
|
|
71
|
-
offProblem = (listener: ProblemListener) => {
|
|
72
|
-
const idx = this.problemListeners.findIndex(val => val === listener)
|
|
73
|
-
|
|
74
|
-
if (idx < 0 || isNaN(idx)) return
|
|
75
|
-
|
|
76
|
-
this.problemListeners.splice(idx, 1)
|
|
77
|
-
}
|
|
78
|
-
}
|
|
@@ -1,71 +0,0 @@
|
|
|
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, otherErrors } from '../defaults/index.js'
|
|
6
|
-
import { defaultInstance } from '../util/defaults.js'
|
|
7
|
-
|
|
8
|
-
export class ExpressMiddleware extends MiddlewareBase {
|
|
9
|
-
name = 'express'
|
|
10
|
-
|
|
11
|
-
middleware = (error: unknown, req: Request, res: Response, next: NextFunction) => {
|
|
12
|
-
this.parse(error)
|
|
13
|
-
.then(problems => {
|
|
14
|
-
for (const problem of problems) {
|
|
15
|
-
if (problem.instance === defaultInstance) {
|
|
16
|
-
problem.instance = req.originalUrl
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
this.emit(problems)
|
|
21
|
-
|
|
22
|
-
res
|
|
23
|
-
.contentType('application/problem+json')
|
|
24
|
-
.status(problems[0]?.status ?? 500)
|
|
25
|
-
.json(problems.map(val => val.toObject()))
|
|
26
|
-
})
|
|
27
|
-
.catch(err => {
|
|
28
|
-
res
|
|
29
|
-
.contentType('application/problem+json')
|
|
30
|
-
.status(500)
|
|
31
|
-
.json([
|
|
32
|
-
new Problem({
|
|
33
|
-
...otherErrors.unknown,
|
|
34
|
-
'instance': req.originalUrl,
|
|
35
|
-
'status': 500,
|
|
36
|
-
'stack': err?.stack,
|
|
37
|
-
'errorObject': err,
|
|
38
|
-
}).toObject()
|
|
39
|
-
])
|
|
40
|
-
|
|
41
|
-
throw err
|
|
42
|
-
})
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* This function applies the middleware to your Express application. Put this at the very end of your middleware/routes.
|
|
47
|
-
* If you want to also handle 404 errors, check out the notFound middleware function.
|
|
48
|
-
* @example app.use(middleware.use())
|
|
49
|
-
*/
|
|
50
|
-
use = () => this.middleware
|
|
51
|
-
|
|
52
|
-
constructor(options?: PartialMiddlewareOptions) {
|
|
53
|
-
super({
|
|
54
|
-
'parsers': []
|
|
55
|
-
}, options)
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* Put this right before the middleware.use() to catch all 404 errors and respond with a Problem instead of the default Express response
|
|
60
|
-
* @example app.use(middleware.notFound)
|
|
61
|
-
*/
|
|
62
|
-
notFound = (req: Request, res: Response, next: NextFunction) => {
|
|
63
|
-
const error = new Problem({
|
|
64
|
-
...codes4xx[404],
|
|
65
|
-
'instance': req.originalUrl,
|
|
66
|
-
'detail': `No API endpoints exist on ${req.url} for request method ${req.method}`
|
|
67
|
-
})
|
|
68
|
-
|
|
69
|
-
return next(error)
|
|
70
|
-
}
|
|
71
|
-
}
|
package/src/parsers/axios.ts
DELETED
|
@@ -1,60 +0,0 @@
|
|
|
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 { getHttpError } from '../util/misc.js'
|
|
6
|
-
import type { AxiosError } from 'axios'
|
|
7
|
-
|
|
8
|
-
function isAxiosError<T = unknown, D = unknown>(payload: unknown): payload is AxiosError<T, D> {
|
|
9
|
-
return typeof payload === 'object' && payload !== null && 'isAxiosError' in payload && payload.isAxiosError === true
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
const parse: Parser = (input) => {
|
|
13
|
-
if (typeof input !== 'object' || input == null) return []
|
|
14
|
-
if (!isAxiosError(input)) return []
|
|
15
|
-
const request = input.request
|
|
16
|
-
|
|
17
|
-
if (input?.response?.data) {
|
|
18
|
-
const problems = getProblems(input?.response?.data)
|
|
19
|
-
if (problems?.length) return problems
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
if (input?.response) {
|
|
23
|
-
const opts = getHttpError(input.response.status)
|
|
24
|
-
if (opts) return [new Problem({
|
|
25
|
-
...opts,
|
|
26
|
-
'instance': input?.config?.url,
|
|
27
|
-
'stack': input?.stack,
|
|
28
|
-
'errorObject': input
|
|
29
|
-
})]
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
// TODO: Fix this, this seems bad
|
|
33
|
-
if (request) {
|
|
34
|
-
const isNode = typeof process !== 'undefined' && process.versions && process.versions.node
|
|
35
|
-
const isBrowser = typeof window !== 'undefined' && typeof window.document !== 'undefined'
|
|
36
|
-
|
|
37
|
-
if (isBrowser && request instanceof XMLHttpRequest) {
|
|
38
|
-
return [new Problem({
|
|
39
|
-
...otherErrors.networkError,
|
|
40
|
-
'instance': input?.config?.url,
|
|
41
|
-
'stack': input?.stack,
|
|
42
|
-
'errorObject': input
|
|
43
|
-
})]
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// Probably a ClientRequest
|
|
47
|
-
if (isNode && 'pipe' in request && 'destroy' in request) {
|
|
48
|
-
return [new Problem({
|
|
49
|
-
...otherErrors.networkError,
|
|
50
|
-
'instance': input?.config?.url,
|
|
51
|
-
'stack': input?.stack,
|
|
52
|
-
'errorObject': input
|
|
53
|
-
})]
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
return []
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
export default parse
|
|
@@ -1,107 +0,0 @@
|
|
|
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[] = [
|
|
12
|
-
{
|
|
13
|
-
'search': 'invalid token',
|
|
14
|
-
'result': {
|
|
15
|
-
'type': '/errors/jsonwebtoken/invalidtoken',
|
|
16
|
-
'status': 400,
|
|
17
|
-
'title': 'Invalid JSON Web Token',
|
|
18
|
-
}
|
|
19
|
-
},
|
|
20
|
-
{
|
|
21
|
-
'search': 'jwt malformed',
|
|
22
|
-
'result': {
|
|
23
|
-
'type': '/errors/jsonwebtoken/malformed',
|
|
24
|
-
'status': 400,
|
|
25
|
-
'title': 'Malformed JSON Web Token',
|
|
26
|
-
}
|
|
27
|
-
},
|
|
28
|
-
{
|
|
29
|
-
'search': 'jwt signature is required',
|
|
30
|
-
'result': {
|
|
31
|
-
'type': '/errors/jsonwebtoken/signaturerequired',
|
|
32
|
-
'status': 400,
|
|
33
|
-
'title': 'JSON Web Token is missing, but required',
|
|
34
|
-
}
|
|
35
|
-
},
|
|
36
|
-
{
|
|
37
|
-
'search': 'invalid signature',
|
|
38
|
-
'result': {
|
|
39
|
-
'type': '/errors/jsonwebtoken/invalidsignature',
|
|
40
|
-
'status': 400,
|
|
41
|
-
'title': 'The JSON Web Token signature is invalid',
|
|
42
|
-
}
|
|
43
|
-
},
|
|
44
|
-
{
|
|
45
|
-
'search': /^(jwt audience invalid\. expected:)/,
|
|
46
|
-
'result': {
|
|
47
|
-
'type': '/errors/jsonwebtoken/invalidaud',
|
|
48
|
-
'status': 400,
|
|
49
|
-
'title': 'The JSON Web Token audience is invalid',
|
|
50
|
-
}
|
|
51
|
-
},
|
|
52
|
-
{
|
|
53
|
-
'search': /^(jwt issuer invalid\. expected:)/,
|
|
54
|
-
'result': {
|
|
55
|
-
'type': '/errors/jsonwebtoken/invalidiss',
|
|
56
|
-
'status': 400,
|
|
57
|
-
'title': 'The JSON Web Token issuer is invalid',
|
|
58
|
-
}
|
|
59
|
-
},
|
|
60
|
-
{
|
|
61
|
-
'search': /^(jwt id invalid\. expected:)/,
|
|
62
|
-
'result': {
|
|
63
|
-
'type': '/errors/jsonwebtoken/invalidid',
|
|
64
|
-
'status': 400,
|
|
65
|
-
'title': 'The JSON Web Token id is invalid',
|
|
66
|
-
}
|
|
67
|
-
},
|
|
68
|
-
{
|
|
69
|
-
'search': /^(jwt subject invalid\. expected:)/,
|
|
70
|
-
'result': {
|
|
71
|
-
'type': '/errors/jsonwebtoken/invalidsubject',
|
|
72
|
-
'status': 400,
|
|
73
|
-
'title': 'The JSON Web Token subject is invalid',
|
|
74
|
-
}
|
|
75
|
-
}
|
|
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
|
-
|
|
82
|
-
if (found) {
|
|
83
|
-
return [new Problem({
|
|
84
|
-
...found.result,
|
|
85
|
-
'errorObject': input,
|
|
86
|
-
})]
|
|
87
|
-
}
|
|
88
|
-
} else if (input instanceof TokenExpiredError && input.name === 'TokenExpiredError') {
|
|
89
|
-
return [new Problem({
|
|
90
|
-
'title': 'The JSON Web Token has expired',
|
|
91
|
-
'type': '/errors/jsonwebtoken/tokenexpired',
|
|
92
|
-
'detail': `The JSON Web Token expired at ${input.expiredAt}`,
|
|
93
|
-
'status': 400,
|
|
94
|
-
'errorObject': input,
|
|
95
|
-
})]
|
|
96
|
-
} else if (input instanceof NotBeforeError && input.name === 'NotBeforeError') {
|
|
97
|
-
return [new Problem({
|
|
98
|
-
'title': 'The JSON Web Token is not valid yet',
|
|
99
|
-
'type': '/errors/jsonwebtoken/notbefore',
|
|
100
|
-
'detail': `The JSON Web Token is not valid before ${input.date}`,
|
|
101
|
-
'status': 400,
|
|
102
|
-
'errorObject': input,
|
|
103
|
-
})]
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
export default parse
|
package/src/parsers/mikroorm.ts
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
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
|
-
})
|
|
18
|
-
]
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export default parse
|
package/src/parsers/tsoa.ts
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import { ValidateError } from 'tsoa'
|
|
2
|
-
import { Problem } from '../problem.js'
|
|
3
|
-
import { otherErrors } from '../defaults/others.js'
|
|
4
|
-
import type { Parser } from '../typings/parser.js'
|
|
5
|
-
|
|
6
|
-
const parse: Parser = (input) => {
|
|
7
|
-
if (!(input instanceof ValidateError)) {
|
|
8
|
-
return
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
// TODO: Give actual useful responses instead of this shit
|
|
12
|
-
return [
|
|
13
|
-
new Problem({
|
|
14
|
-
...otherErrors.inputValidationError,
|
|
15
|
-
'detail': input.message,
|
|
16
|
-
'status': input.status,
|
|
17
|
-
'data': {
|
|
18
|
-
'fields': input.fields,
|
|
19
|
-
},
|
|
20
|
-
'stack': input.stack,
|
|
21
|
-
'errorObject': input
|
|
22
|
-
})
|
|
23
|
-
]
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export default parse
|
package/src/parsers/zod.ts
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import { ZodError } from 'zod'
|
|
2
|
-
import type { Parser } from '../typings/parser.js'
|
|
3
|
-
import { Problem } from '../problem.js'
|
|
4
|
-
import { otherErrors } from '../defaults/others.js'
|
|
5
|
-
|
|
6
|
-
const parse: Parser = (input) => {
|
|
7
|
-
if (!(input instanceof ZodError)) {
|
|
8
|
-
return
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
return [new Problem({
|
|
12
|
-
...otherErrors.inputValidationError,
|
|
13
|
-
'detail': `Got ${input.issues.length} validation errors`,
|
|
14
|
-
'status': 400,
|
|
15
|
-
'stack': input.stack,
|
|
16
|
-
'errorObject': input,
|
|
17
|
-
'data': {
|
|
18
|
-
'issues': input.issues
|
|
19
|
-
}
|
|
20
|
-
})]
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export default parse
|
package/src/problem.ts
DELETED
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
import type { IProblem, ProblemOpts, JsonProblem } from './typings/problem.js'
|
|
2
|
-
import { defaultDetail, defaultInstance, defaultTitle, defaultType } from './util/defaults.js'
|
|
3
|
-
import { version } from './util/version.js'
|
|
4
|
-
|
|
5
|
-
export class Problem extends Error implements IProblem {
|
|
6
|
-
public type = defaultType
|
|
7
|
-
public title = defaultTitle
|
|
8
|
-
public status = 500
|
|
9
|
-
public detail = defaultDetail
|
|
10
|
-
public instance = defaultInstance
|
|
11
|
-
public errorObject?: unknown
|
|
12
|
-
public __problemVersion = version
|
|
13
|
-
public data: unknown = undefined
|
|
14
|
-
public middleware?: string
|
|
15
|
-
|
|
16
|
-
constructor({ type, title, status, detail, instance, stack, errorObject, data, middleware }: ProblemOpts) {
|
|
17
|
-
super(detail ?? title ?? 'HTTP Problem Details')
|
|
18
|
-
// TODO: Figure out why i wrote this?
|
|
19
|
-
Object.setPrototypeOf(this, new.target.prototype)
|
|
20
|
-
|
|
21
|
-
this.type = type
|
|
22
|
-
this.title = title
|
|
23
|
-
this.status = status
|
|
24
|
-
|
|
25
|
-
if (detail) this.detail = detail
|
|
26
|
-
if (instance) this.instance = instance
|
|
27
|
-
if (errorObject) this.errorObject = errorObject
|
|
28
|
-
if (middleware) this.middleware = middleware
|
|
29
|
-
|
|
30
|
-
if (stack) {
|
|
31
|
-
this.stack = stack
|
|
32
|
-
} else if (errorObject && typeof errorObject === 'object' && 'stack' in errorObject && typeof errorObject.stack === 'string') {
|
|
33
|
-
this.stack = errorObject.stack
|
|
34
|
-
} else {
|
|
35
|
-
if (typeof Error.captureStackTrace === 'function') {
|
|
36
|
-
Error.captureStackTrace(this, this.constructor)
|
|
37
|
-
} else {
|
|
38
|
-
this.stack = new Error(detail ?? title ?? 'HTTP Problem Details').stack
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
if (data) this.data = data
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
public toObject = (): JsonProblem => {
|
|
46
|
-
return {
|
|
47
|
-
'type': this.type,
|
|
48
|
-
'title': this.title,
|
|
49
|
-
'status': this.status,
|
|
50
|
-
'detail': this.detail,
|
|
51
|
-
'instance': this.instance,
|
|
52
|
-
'__problemVersion': this.__problemVersion,
|
|
53
|
-
'data': this.data
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
}
|
package/src/typings/codes.ts
DELETED
package/src/typings/index.ts
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import type { Problem } from '../problem.js'
|
|
2
|
-
import type { Parser } from './parser.js'
|
|
3
|
-
|
|
4
|
-
export interface MiddlewareOptions {
|
|
5
|
-
/**
|
|
6
|
-
* A list of parsers to be used by the middleware.
|
|
7
|
-
* Parsers are run in the order they sit in the array
|
|
8
|
-
*/
|
|
9
|
-
parsers: Parser[]
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export type PartialMiddlewareOptions = Partial<MiddlewareOptions>
|
|
13
|
-
|
|
14
|
-
export type ProblemListener = (problems: Problem[]) => void
|
package/src/typings/parser.ts
DELETED
package/src/typings/problem.ts
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @param {string} type - A URI refrence that identifies the problem type.
|
|
3
|
-
* @param {string} title - A short, human-readable summary of the problem
|
|
4
|
-
* type. It SHOULD NOT change from occurrence to occurrence of the problem
|
|
5
|
-
* @param {number} status - The HTTP status code
|
|
6
|
-
* @param {string} detail - A human-readable explanation specific to this
|
|
7
|
-
occurrence of the problem.
|
|
8
|
-
* @property {string} instance - A URI reference that identifies the specific
|
|
9
|
-
occurrence of the problem
|
|
10
|
-
**/
|
|
11
|
-
|
|
12
|
-
export interface IProblem {
|
|
13
|
-
type: string
|
|
14
|
-
title: string
|
|
15
|
-
status: number
|
|
16
|
-
detail?: string
|
|
17
|
-
instance?: string
|
|
18
|
-
stack?: string
|
|
19
|
-
errorObject?: unknown
|
|
20
|
-
__problemVersion: string
|
|
21
|
-
data?: unknown
|
|
22
|
-
middleware?: string
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export type JsonProblem = Omit<IProblem, 'stack' | 'errorObject' | 'middleware'>
|
|
26
|
-
|
|
27
|
-
export type ProblemOpts = Omit<IProblem, '__problemVersion'>
|
package/src/util/defaults.ts
DELETED
package/src/util/getProblems.ts
DELETED
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
import { Problem } from '../problem.js'
|
|
2
|
-
import { Parser } from '../typings/parser.js'
|
|
3
|
-
import type { JsonProblem } from '../typings/problem.js'
|
|
4
|
-
import { isProblemArray } from './problemArray.js'
|
|
5
|
-
import { isCompatibleVersion } from './version.js'
|
|
6
|
-
|
|
7
|
-
const strings = ['title', 'type', 'instance', 'detail', '__problemVersion'] as const
|
|
8
|
-
export function isJsonProblem(input: unknown): input is JsonProblem {
|
|
9
|
-
if (input === null) return false
|
|
10
|
-
if (typeof input !== 'object') return false
|
|
11
|
-
|
|
12
|
-
if (!strings.every(val => val in input && typeof input[val as keyof typeof input] === 'string')) return false
|
|
13
|
-
if (!isCompatibleVersion(('__problemVersion' in input && typeof input['__problemVersion'] === 'string') ? input['__problemVersion'] : '')) return false
|
|
14
|
-
|
|
15
|
-
if (!('status' in input) || typeof input.status !== 'number') return false
|
|
16
|
-
|
|
17
|
-
return true
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export function createFromJson({ title, type, instance, detail, status, data }: JsonProblem): Problem {
|
|
21
|
-
return new Problem({ title, type, instance, detail, status, data })
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Get an array of Problems from a given input.
|
|
26
|
-
* Handles existing Problems, arrays of Problems, JSON Problems, JSON Problem arrays and optionally attempts to parse any input with the given parsers.
|
|
27
|
-
* Only returns an array of Problems if it can successfully parse the input.
|
|
28
|
-
*/
|
|
29
|
-
export function getProblems(input: unknown, parsers?: Parser[]): Problem[] | undefined {
|
|
30
|
-
if (typeof input === 'string') {
|
|
31
|
-
try {
|
|
32
|
-
const p = JSON.parse(input)
|
|
33
|
-
input = p
|
|
34
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
35
|
-
} catch (err) { /**/ }
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
if (input instanceof Problem) return [input]
|
|
39
|
-
if (isProblemArray(input, false)) return input
|
|
40
|
-
|
|
41
|
-
if (Array.isArray(input) && input.length > 0 && input.every(val => isJsonProblem(val))) {
|
|
42
|
-
return input.map(val => createFromJson(val))
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
if (isJsonProblem(input)) {
|
|
46
|
-
return [createFromJson(input)]
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
if (parsers) {
|
|
50
|
-
for (const parser of parsers) {
|
|
51
|
-
const problems = parser(input)
|
|
52
|
-
if (!problems || !problems.length) continue
|
|
53
|
-
return problems
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
}
|
package/src/util/index.ts
DELETED
package/src/util/misc.ts
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import * as allCodes from '../defaults/index.js'
|
|
2
|
-
import { ProblemOpts, Codes } from '../typings/index.js'
|
|
3
|
-
|
|
4
|
-
export function getHttpError(statusCode: number | string): ProblemOpts | undefined {
|
|
5
|
-
for (const key in allCodes) {
|
|
6
|
-
// This ensures all codes are written correctly as well, so that's a bonus
|
|
7
|
-
const codeObject: Codes = allCodes[key as keyof typeof allCodes]
|
|
8
|
-
|
|
9
|
-
if (typeof statusCode === 'string') {
|
|
10
|
-
const conv = Number(statusCode)
|
|
11
|
-
if (!isNaN(conv)) statusCode = conv
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
const code = codeObject[statusCode]
|
|
15
|
-
|
|
16
|
-
if (!code) return
|
|
17
|
-
|
|
18
|
-
return code
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
|
package/src/util/problemArray.ts
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import { Problem } from '../problem.js'
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Checks whether a given object is an array of Problem objects
|
|
5
|
-
* @param input Object to test
|
|
6
|
-
* @param allowEmpty If true or undefined, an empty array is considered a Problem array. If false, an empty array is not considered a Problem array.
|
|
7
|
-
*/
|
|
8
|
-
export function isProblemArray(input: unknown, allowEmpty?: boolean): input is Problem[] {
|
|
9
|
-
if (!Array.isArray(input)) return false
|
|
10
|
-
|
|
11
|
-
if (input.length < 1) {
|
|
12
|
-
if (typeof allowEmpty !== 'boolean') {
|
|
13
|
-
console.warn(new Error('@emoyly/problem -> isProblemArray: input is an empty array, and therefore is considered a Problem array. If this is not the intended behavior, consider setting the "onlyWhenContent" argument to true.'))
|
|
14
|
-
return true
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
return allowEmpty
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
return input.every(val => val instanceof Problem)
|
|
21
|
-
}
|
package/src/util/version.ts
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
export const version = '7.0.6'
|
|
2
|
-
export const major = Number(version.split('.')[0])
|
|
3
|
-
|
|
4
|
-
export function isCompatibleVersion(inputVersion: string) {
|
|
5
|
-
// TODO: Remove this
|
|
6
|
-
if (inputVersion === 'lua-dev') return true
|
|
7
|
-
|
|
8
|
-
const parts = inputVersion.split('.')
|
|
9
|
-
const inputMajor = Number(parts[0])
|
|
10
|
-
|
|
11
|
-
if (parts.length !== 3) return false
|
|
12
|
-
if (isNaN(inputMajor)) return false
|
|
13
|
-
if (inputMajor !== major) return false
|
|
14
|
-
|
|
15
|
-
return true
|
|
16
|
-
}
|