@emoyly/problem 7.0.4 → 7.0.5

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.
Files changed (53) hide show
  1. package/README.md +4 -1
  2. package/SECURITY.md +2 -2
  3. package/cjs/middleware/base.js +10 -20
  4. package/cjs/parsers/jsonwebtoken.js +6 -2
  5. package/cjs/parsers/mikroorm.js +1 -1
  6. package/cjs/parsers/tsoa.js +2 -1
  7. package/cjs/parsers/zod.d.ts +3 -0
  8. package/cjs/parsers/zod.js +19 -0
  9. package/cjs/tsconfig.tsbuildinfo +1 -1
  10. package/cjs/typings/parser.d.ts +1 -1
  11. package/cjs/util/getProblems.d.ts +7 -1
  12. package/cjs/util/getProblems.js +19 -6
  13. package/cjs/util/index.d.ts +1 -1
  14. package/cjs/util/index.js +1 -1
  15. package/cjs/util/problemArray.d.ts +7 -0
  16. package/cjs/util/problemArray.js +21 -0
  17. package/cjs/util/version.d.ts +1 -1
  18. package/cjs/util/version.js +1 -1
  19. package/esm/middleware/base.js +10 -20
  20. package/esm/parsers/jsonwebtoken.js +6 -2
  21. package/esm/parsers/mikroorm.js +1 -1
  22. package/esm/parsers/tsoa.js +2 -1
  23. package/esm/parsers/zod.d.ts +3 -0
  24. package/esm/parsers/zod.js +17 -0
  25. package/esm/tsconfig.tsbuildinfo +1 -1
  26. package/esm/typings/parser.d.ts +1 -1
  27. package/esm/util/getProblems.d.ts +7 -1
  28. package/esm/util/getProblems.js +19 -6
  29. package/esm/util/index.d.ts +1 -1
  30. package/esm/util/index.js +1 -1
  31. package/esm/util/problemArray.d.ts +7 -0
  32. package/esm/util/problemArray.js +18 -0
  33. package/esm/util/version.d.ts +1 -1
  34. package/esm/util/version.js +1 -1
  35. package/package.json +28 -17
  36. package/scripts/ensureCorrectVersion.js +1 -1
  37. package/src/middleware/base.ts +9 -21
  38. package/src/parsers/axios.ts +0 -1
  39. package/src/parsers/jsonwebtoken.ts +6 -3
  40. package/src/parsers/mikroorm.ts +1 -1
  41. package/src/parsers/tsoa.ts +2 -1
  42. package/src/parsers/zod.ts +21 -0
  43. package/src/typings/parser.ts +1 -1
  44. package/src/util/getProblems.ts +21 -8
  45. package/src/util/index.ts +1 -1
  46. package/src/util/misc.ts +1 -0
  47. package/src/util/problemArray.ts +21 -0
  48. package/src/util/version.ts +1 -1
  49. package/cjs/util/isProblemArray.d.ts +0 -2
  50. package/cjs/util/isProblemArray.js +0 -9
  51. package/esm/util/isProblemArray.d.ts +0 -2
  52. package/esm/util/isProblemArray.js +0 -6
  53. package/src/util/isProblemArray.ts +0 -6
@@ -1,4 +1,4 @@
1
- export const version = '7.0.4';
1
+ export const version = '7.0.5';
2
2
  export const major = Number(version.split('.')[0]);
3
3
  export function isCompatibleVersion(inputVersion) {
4
4
  // TODO: Remove this
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@emoyly/problem",
3
- "version": "7.0.4",
4
- "description": "A simple error library based around the RFC-7807 standard with optional support for Sentry.io and Express",
3
+ "version": "7.0.5",
4
+ "description": "A simple error library based around the RFC-7807 standard with optional support for different parsers and middleware.",
5
5
  "main": "cjs/index.js",
6
6
  "repository": "https://github.com/emoyly/problem",
7
7
  "author": "Emil Petersen <emoyly@gmail.com>",
@@ -9,29 +9,33 @@
9
9
  "type": "module",
10
10
  "scripts": {
11
11
  "build": "yarn dualBuild",
12
- "lint": "eslint src",
12
+ "lint": "eslint src && node ./scripts/ensureCorrectVersion.js",
13
13
  "publish": "yarn npm publish --access public --tolerate-republish"
14
14
  },
15
15
  "devDependencies": {
16
- "@emoyly/devutils": "^1.0.4",
17
- "@emoyly/eslint-config": "^1.0.1",
18
- "@emoyly/utils": "^1.0.1",
19
- "@mikro-orm/core": "^6.2.9",
20
- "@types/express": "^4.17.21",
21
- "@types/jsonwebtoken": "^9.0.6",
22
- "@types/node": "^20.14.11",
23
- "axios": "^1.7.2",
24
- "eslint": "^9.7.0",
16
+ "@emoyly/devutils": "^1.0.5",
17
+ "@emoyly/eslint-config": "^1.1.2",
18
+ "@emoyly/utils": "^1.0.4",
19
+ "@mikro-orm/core": "^6.4.0",
20
+ "@stylistic/eslint-plugin-js": "^2.11.0",
21
+ "@stylistic/eslint-plugin-ts": "^2.11.0",
22
+ "@types/express": "^5.0.0",
23
+ "@types/jsonwebtoken": "^9.0.7",
24
+ "@types/node": "^22.10.1",
25
+ "axios": "^1.7.8",
26
+ "eslint": "^9.16.0",
25
27
  "jsonwebtoken": "^9.0.2",
26
- "tsoa": "^6.4.0",
27
- "typescript": "^5.5.3",
28
- "typescript-eslint": "^7.16.1"
28
+ "tsoa": "^6.5.1",
29
+ "typescript": "^5.7.2",
30
+ "typescript-eslint": "^8.17.0",
31
+ "zod": "^3.23.8"
29
32
  },
30
33
  "peerDependencies": {
31
34
  "@mikro-orm/core": "*",
32
35
  "axios": "*",
33
36
  "jsonwebtoken": "*",
34
- "tsoa": "*"
37
+ "tsoa": "*",
38
+ "zod": "*"
35
39
  },
36
40
  "peerDependenciesMeta": {
37
41
  "@mikro-orm/core": {
@@ -45,9 +49,12 @@
45
49
  },
46
50
  "tsoa": {
47
51
  "optional": true
52
+ },
53
+ "zod": {
54
+ "optional": true
48
55
  }
49
56
  },
50
- "packageManager": "yarn@4.3.1",
57
+ "packageManager": "yarn@4.5.3",
51
58
  "publishConfig": {
52
59
  "access": "public",
53
60
  "registry": "https://registry.npmjs.org"
@@ -84,6 +91,10 @@
84
91
  "./parsers/tsoa": {
85
92
  "import": "./esm/parsers/tsoa.js",
86
93
  "require": "./cjs/parsers/tsoa.js"
94
+ },
95
+ "./parsers/zod": {
96
+ "import": "./esm/parsers/zod.js",
97
+ "require": "./cjs/parsers/zod.js"
87
98
  }
88
99
  }
89
100
  }
@@ -17,4 +17,4 @@ if (match[0] !== version){
17
17
  throw new Error(`Version in version.ts (${match[0]}) does not match package.json (${version})`)
18
18
  }
19
19
 
20
- console.log(`Version in version.ts matches package.json: ${version}`)
20
+ //console.log(`Version in version.ts matches package.json: ${version}`)
@@ -28,29 +28,17 @@ export abstract class MiddlewareBase {
28
28
  * Parse input using parsers
29
29
  */
30
30
  parse = async (input: unknown): Promise<Problem[]> => {
31
- const problems: Problem[] = []
31
+ const problems = getProblems(input, this.options.parsers)
32
32
 
33
- const prob = getProblems(input)
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
+ })]
34
40
 
35
- if (!prob) {
36
- for (const parse of this.options.parsers) {
37
- const resp = parse(input)
38
-
39
- if (!resp.length) continue
40
-
41
- problems.push(...resp)
42
- break
43
- }
44
-
45
- if (!problems.length && this.enableFallback) {
46
- problems.push(new Problem({
47
- ...this.fallback,
48
- 'errorObject': input,
49
- 'stack': (typeof input === 'object' && input !== null && 'stack' in input && typeof input.stack === 'string') ? input.stack : undefined
50
- }))
51
- }
52
- } else {
53
- problems.push(...prob)
41
+ return []
54
42
  }
55
43
 
56
44
  for (const p of problems) {
@@ -9,7 +9,6 @@ function isAxiosError<T = unknown, D = unknown>(payload: unknown): payload is Ax
9
9
  return typeof payload === 'object' && payload !== null && 'isAxiosError' in payload && payload.isAxiosError === true
10
10
  }
11
11
 
12
-
13
12
  const parse: Parser = (input) => {
14
13
  if (typeof input !== 'object' || input == null) return []
15
14
  if (!isAxiosError(input)) return []
@@ -80,7 +80,10 @@ const parse: Parser = (input) => {
80
80
  const found = errorMap.find(val => typeof val.search === 'string' ? val.search === input.message : val.search.test(input.message))
81
81
 
82
82
  if (found) {
83
- return [new Problem(found.result)]
83
+ return [new Problem({
84
+ ...found.result,
85
+ 'errorObject': input,
86
+ })]
84
87
  }
85
88
  } else if (input instanceof TokenExpiredError && input.name === 'TokenExpiredError') {
86
89
  return [new Problem({
@@ -88,6 +91,7 @@ const parse: Parser = (input) => {
88
91
  'type': '/errors/jsonwebtoken/tokenexpired',
89
92
  'detail': `The JSON Web Token expired at ${input.expiredAt}`,
90
93
  'status': 400,
94
+ 'errorObject': input,
91
95
  })]
92
96
  } else if (input instanceof NotBeforeError && input.name === 'NotBeforeError') {
93
97
  return [new Problem({
@@ -95,10 +99,9 @@ const parse: Parser = (input) => {
95
99
  'type': '/errors/jsonwebtoken/notbefore',
96
100
  'detail': `The JSON Web Token is not valid before ${input.date}`,
97
101
  'status': 400,
102
+ 'errorObject': input,
98
103
  })]
99
104
  }
100
-
101
- return []
102
105
  }
103
106
 
104
107
  export default parse
@@ -6,7 +6,7 @@ import { NotFoundError } from '@mikro-orm/core'
6
6
  // TODO: Make this less bad
7
7
  const parse: Parser = (input) => {
8
8
  if (!(input instanceof NotFoundError)) {
9
- return []
9
+ return
10
10
  }
11
11
 
12
12
  return [
@@ -5,7 +5,7 @@ import type { Parser } from '../typings/parser.js'
5
5
 
6
6
  const parse: Parser = (input) => {
7
7
  if (!(input instanceof ValidateError)) {
8
- return []
8
+ return
9
9
  }
10
10
 
11
11
  // TODO: Give actual useful responses instead of this shit
@@ -18,6 +18,7 @@ const parse: Parser = (input) => {
18
18
  'fields': input.fields,
19
19
  },
20
20
  'stack': input.stack,
21
+ 'errorObject': input
21
22
  })
22
23
  ]
23
24
  }
@@ -0,0 +1,21 @@
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': input.message,
14
+ 'status': 400,
15
+ 'stack': input.stack,
16
+ 'errorObject': input,
17
+ 'data': input.issues
18
+ })]
19
+ }
20
+
21
+ export default parse
@@ -1,3 +1,3 @@
1
1
  import type { Problem } from '../problem.js'
2
2
 
3
- export type Parser = (input: unknown) => Problem[]
3
+ export type Parser = (input: unknown) => Problem[] | undefined
@@ -1,9 +1,10 @@
1
1
  import { Problem } from '../problem.js'
2
+ import { Parser } from '../typings/parser.js'
2
3
  import type { JsonProblem } from '../typings/problem.js'
3
- import { isProblemArray } from './isProblemArray.js'
4
+ import { isProblemArray } from './problemArray.js'
4
5
  import { isCompatibleVersion } from './version.js'
5
6
 
6
- const strings = ['title', 'type', 'instance', 'detail', '__problemVersion']
7
+ const strings = ['title', 'type', 'instance', 'detail', '__problemVersion'] as const
7
8
  export function isJsonProblem(input: unknown): input is JsonProblem {
8
9
  if (input === null) return false
9
10
  if (typeof input !== 'object') return false
@@ -20,24 +21,36 @@ export function createFromJson({ title, type, instance, detail, status, data }:
20
21
  return new Problem({ title, type, instance, detail, status, data })
21
22
  }
22
23
 
23
- export function getProblems(input: unknown): Problem[] | void {
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 {
24
30
  if (typeof input === 'string') {
25
31
  try {
26
32
  const p = JSON.parse(input)
27
33
  input = p
34
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
28
35
  } catch (err) { /**/ }
29
36
  }
30
37
 
31
38
  if (input instanceof Problem) return [input]
32
- if (isProblemArray(input)) return input
39
+ if (isProblemArray(input, false)) return input
33
40
 
34
- if (Array.isArray(input) && input.every(val => isJsonProblem(val))) {
35
- const _input = input as JsonProblem[]
36
-
37
- return _input.map(val => createFromJson(val))
41
+ if (Array.isArray(input) && input.length > 0 && input.every(val => isJsonProblem(val))) {
42
+ return input.map(val => createFromJson(val))
38
43
  }
39
44
 
40
45
  if (isJsonProblem(input)) {
41
46
  return [createFromJson(input)]
42
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
+ }
43
56
  }
package/src/util/index.ts CHANGED
@@ -1,4 +1,4 @@
1
1
  export * from './getProblems.js'
2
2
  export * from './defaults.js'
3
- export * from './isProblemArray.js'
3
+ export * from './problemArray.js'
4
4
  export * from './misc.js'
package/src/util/misc.ts CHANGED
@@ -18,3 +18,4 @@ export function getHttpError(statusCode: number | string): ProblemOpts | undefin
18
18
  return code
19
19
  }
20
20
  }
21
+
@@ -0,0 +1,21 @@
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
+ }
@@ -1,4 +1,4 @@
1
- export const version = '7.0.4'
1
+ export const version = '7.0.5'
2
2
  export const major = Number(version.split('.')[0])
3
3
 
4
4
  export function isCompatibleVersion(inputVersion: string) {
@@ -1,2 +0,0 @@
1
- import { Problem } from '../problem.js';
2
- export declare function isProblemArray(input: unknown): input is Problem[];
@@ -1,9 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.isProblemArray = isProblemArray;
4
- const problem_js_1 = require("../problem.js");
5
- function isProblemArray(input) {
6
- if (!Array.isArray(input))
7
- return false;
8
- return input.every(val => val instanceof problem_js_1.Problem);
9
- }
@@ -1,2 +0,0 @@
1
- import { Problem } from '../problem.js';
2
- export declare function isProblemArray(input: unknown): input is Problem[];
@@ -1,6 +0,0 @@
1
- import { Problem } from '../problem.js';
2
- export function isProblemArray(input) {
3
- if (!Array.isArray(input))
4
- return false;
5
- return input.every(val => val instanceof Problem);
6
- }
@@ -1,6 +0,0 @@
1
- import { Problem } from '../problem.js'
2
-
3
- export function isProblemArray(input: unknown): input is Problem[] {
4
- if (!Array.isArray(input)) return false
5
- return input.every(val => val instanceof Problem)
6
- }