@emoyly/problem 5.0.8 → 7.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.
Files changed (169) hide show
  1. package/.editorconfig +11 -0
  2. package/.vscode/extensions.json +7 -0
  3. package/.vscode/settings.json +2 -0
  4. package/.yarn/versions/524dd037.yml +0 -0
  5. package/.yarn/versions/b57d536b.yml +0 -0
  6. package/.yarn/versions/d984e272.yml +0 -0
  7. package/README.md +1 -1
  8. package/cjs/defaults/index.d.ts +7 -0
  9. package/cjs/defaults/index.js +17 -0
  10. package/cjs/index.d.ts +4 -0
  11. package/cjs/index.js +22 -0
  12. package/{middleware → cjs/middleware}/axios.d.ts +2 -2
  13. package/cjs/middleware/axios.js +23 -0
  14. package/{middleware → cjs/middleware}/base.d.ts +13 -3
  15. package/cjs/middleware/base.js +73 -0
  16. package/{middleware → cjs/middleware}/express.d.ts +2 -2
  17. package/cjs/middleware/express.js +64 -0
  18. package/cjs/package.json +3 -0
  19. package/cjs/parsers/axios.d.ts +3 -0
  20. package/cjs/parsers/axios.js +55 -0
  21. package/cjs/parsers/jsonwebtoken.d.ts +3 -0
  22. package/{parsers → cjs/parsers}/jsonwebtoken.js +4 -4
  23. package/cjs/parsers/mikroorm.d.ts +3 -0
  24. package/{parsers → cjs/parsers}/mikroorm.js +7 -3
  25. package/cjs/parsers/tsoa.d.ts +3 -0
  26. package/{parsers → cjs/parsers}/tsoa.js +10 -4
  27. package/{problem.d.ts → cjs/problem.d.ts} +1 -1
  28. package/{problem.js → cjs/problem.js} +24 -23
  29. package/cjs/tsconfig.tsbuildinfo +1 -0
  30. package/{typings → cjs/typings}/codes.d.ts +1 -1
  31. package/cjs/typings/index.d.ts +4 -0
  32. package/{typings → cjs/typings}/index.js +4 -5
  33. package/{typings → cjs/typings}/middleware.d.ts +3 -1
  34. package/{typings → cjs/typings}/parser.d.ts +1 -1
  35. package/{util → cjs/util}/getProblems.d.ts +2 -2
  36. package/{util → cjs/util}/getProblems.js +10 -11
  37. package/{util → cjs/util}/isProblemArray.d.ts +1 -1
  38. package/{util → cjs/util}/isProblemArray.js +3 -4
  39. package/{util → cjs/util}/misc.d.ts +1 -1
  40. package/cjs/util/misc.js +19 -0
  41. package/{util → cjs/util}/version.d.ts +1 -1
  42. package/{util → cjs/util}/version.js +3 -3
  43. package/eslint.config.mjs +5 -0
  44. package/esm/defaults/4xx.d.ts +147 -0
  45. package/esm/defaults/4xx.js +148 -0
  46. package/esm/defaults/5xx.d.ts +57 -0
  47. package/esm/defaults/5xx.js +58 -0
  48. package/esm/defaults/aws.d.ts +12 -0
  49. package/esm/defaults/aws.js +13 -0
  50. package/esm/defaults/cloudflare.d.ts +47 -0
  51. package/esm/defaults/cloudflare.js +49 -0
  52. package/esm/defaults/iis.d.ts +17 -0
  53. package/esm/defaults/iis.js +18 -0
  54. package/esm/defaults/index.d.ts +7 -0
  55. package/esm/defaults/index.js +7 -0
  56. package/esm/defaults/nginx.d.ts +32 -0
  57. package/esm/defaults/nginx.js +33 -0
  58. package/esm/defaults/others.d.ts +37 -0
  59. package/esm/defaults/others.js +37 -0
  60. package/esm/index.d.ts +4 -0
  61. package/esm/index.js +4 -0
  62. package/esm/middleware/axios.d.ts +13 -0
  63. package/esm/middleware/axios.js +19 -0
  64. package/esm/middleware/base.d.ts +34 -0
  65. package/esm/middleware/base.js +69 -0
  66. package/esm/middleware/express.d.ts +19 -0
  67. package/esm/middleware/express.js +60 -0
  68. package/esm/package.json +3 -0
  69. package/esm/parsers/axios.d.ts +3 -0
  70. package/esm/parsers/axios.js +53 -0
  71. package/esm/parsers/jsonwebtoken.d.ts +3 -0
  72. package/esm/parsers/jsonwebtoken.js +94 -0
  73. package/esm/parsers/mikroorm.d.ts +3 -0
  74. package/esm/parsers/mikroorm.js +17 -0
  75. package/esm/parsers/tsoa.d.ts +3 -0
  76. package/esm/parsers/tsoa.js +21 -0
  77. package/esm/problem.d.ts +14 -0
  78. package/esm/problem.js +56 -0
  79. package/esm/tsconfig.tsbuildinfo +1 -0
  80. package/esm/typings/codes.d.ts +5 -0
  81. package/esm/typings/codes.js +1 -0
  82. package/esm/typings/index.d.ts +4 -0
  83. package/esm/typings/index.js +4 -0
  84. package/esm/typings/middleware.d.ts +11 -0
  85. package/esm/typings/middleware.js +1 -0
  86. package/esm/typings/parser.d.ts +2 -0
  87. package/esm/typings/parser.js +1 -0
  88. package/esm/typings/problem.d.ts +24 -0
  89. package/esm/typings/problem.js +11 -0
  90. package/esm/util/defaults.d.ts +4 -0
  91. package/esm/util/defaults.js +4 -0
  92. package/esm/util/getProblems.d.ts +5 -0
  93. package/esm/util/getProblems.js +40 -0
  94. package/esm/util/isProblemArray.d.ts +2 -0
  95. package/esm/util/isProblemArray.js +6 -0
  96. package/esm/util/misc.d.ts +2 -0
  97. package/esm/util/misc.js +16 -0
  98. package/esm/util/version.d.ts +3 -0
  99. package/esm/util/version.js +16 -0
  100. package/package.json +51 -28
  101. package/scripts/ensureCorrectVersion.js +8 -4
  102. package/src/defaults/4xx.ts +149 -0
  103. package/src/defaults/5xx.ts +59 -0
  104. package/src/defaults/aws.ts +14 -0
  105. package/src/defaults/cloudflare.ts +50 -0
  106. package/src/defaults/iis.ts +19 -0
  107. package/src/defaults/index.ts +7 -0
  108. package/src/defaults/nginx.ts +34 -0
  109. package/src/defaults/others.ts +37 -0
  110. package/src/index.ts +4 -0
  111. package/src/middleware/axios.ts +28 -0
  112. package/src/middleware/base.ts +90 -0
  113. package/src/middleware/express.ts +71 -0
  114. package/src/parsers/axios.ts +61 -0
  115. package/src/parsers/jsonwebtoken.ts +104 -0
  116. package/src/parsers/mikroorm.ts +21 -0
  117. package/src/parsers/tsoa.ts +25 -0
  118. package/src/problem.ts +56 -0
  119. package/src/typings/codes.ts +6 -0
  120. package/src/typings/index.ts +4 -0
  121. package/src/typings/middleware.ts +14 -0
  122. package/src/typings/parser.ts +3 -0
  123. package/src/typings/problem.ts +27 -0
  124. package/src/util/defaults.ts +4 -0
  125. package/src/util/getProblems.ts +43 -0
  126. package/src/util/isProblemArray.ts +6 -0
  127. package/src/util/misc.ts +20 -0
  128. package/src/util/version.ts +16 -0
  129. package/tsconfig.json +15 -0
  130. package/defaults/index.d.ts +0 -7
  131. package/defaults/index.js +0 -17
  132. package/index.d.ts +0 -5
  133. package/index.js +0 -39
  134. package/middleware/axios.js +0 -36
  135. package/middleware/base.js +0 -56
  136. package/middleware/express.js +0 -60
  137. package/parsers/axios.d.ts +0 -3
  138. package/parsers/axios.js +0 -41
  139. package/parsers/jsonwebtoken.d.ts +0 -3
  140. package/parsers/mikroorm.d.ts +0 -3
  141. package/parsers/tsoa.d.ts +0 -3
  142. package/tsconfig.tsbuildinfo +0 -1
  143. package/typings/events.d.ts +0 -2
  144. package/typings/events.js +0 -2
  145. package/typings/index.d.ts +0 -5
  146. package/util/events.d.ts +0 -16
  147. package/util/events.js +0 -29
  148. package/util/misc.js +0 -43
  149. /package/{defaults → cjs/defaults}/4xx.d.ts +0 -0
  150. /package/{defaults → cjs/defaults}/4xx.js +0 -0
  151. /package/{defaults → cjs/defaults}/5xx.d.ts +0 -0
  152. /package/{defaults → cjs/defaults}/5xx.js +0 -0
  153. /package/{defaults → cjs/defaults}/aws.d.ts +0 -0
  154. /package/{defaults → cjs/defaults}/aws.js +0 -0
  155. /package/{defaults → cjs/defaults}/cloudflare.d.ts +0 -0
  156. /package/{defaults → cjs/defaults}/cloudflare.js +0 -0
  157. /package/{defaults → cjs/defaults}/iis.d.ts +0 -0
  158. /package/{defaults → cjs/defaults}/iis.js +0 -0
  159. /package/{defaults → cjs/defaults}/nginx.d.ts +0 -0
  160. /package/{defaults → cjs/defaults}/nginx.js +0 -0
  161. /package/{defaults → cjs/defaults}/others.d.ts +0 -0
  162. /package/{defaults → cjs/defaults}/others.js +0 -0
  163. /package/{typings → cjs/typings}/codes.js +0 -0
  164. /package/{typings → cjs/typings}/middleware.js +0 -0
  165. /package/{typings → cjs/typings}/parser.js +0 -0
  166. /package/{typings → cjs/typings}/problem.d.ts +0 -0
  167. /package/{typings → cjs/typings}/problem.js +0 -0
  168. /package/{util → cjs/util}/defaults.d.ts +0 -0
  169. /package/{util → cjs/util}/defaults.js +0 -0
@@ -0,0 +1,149 @@
1
+ // Source: https://en.wikipedia.org/wiki/List_of_HTTP_status_codes
2
+
3
+ export const statusCodes = {
4
+ 400: {
5
+ status: 400,
6
+ title: 'Bad Request',
7
+ type: '/errors/defaults/4xx/badrequest',
8
+ },
9
+ 401: {
10
+ status: 401,
11
+ title: 'Unauthorized',
12
+ type: '/errors/defaults/4xx/unauthorized',
13
+ },
14
+ 402: {
15
+ status: 402,
16
+ title: 'Payment Required',
17
+ type: '/errors/defaults/4xx/paymentrequired',
18
+ },
19
+ 403: {
20
+ status: 403,
21
+ title: 'Forbidden',
22
+ type: '/errors/defaults/4xx/forbidden',
23
+ },
24
+ 404: {
25
+ status: 404,
26
+ title: 'Not Found',
27
+ type: '/errors/defaults/4xx/notfound',
28
+ },
29
+ 406: {
30
+ status: 406,
31
+ title: 'Not Acceptable',
32
+ type: 'errors/defaults/4xx/notacceptable'
33
+ },
34
+ 405: {
35
+ status: 405,
36
+ title: 'Method Not Allowed',
37
+ type: 'errors/defaults/4xx/methodnotallowed'
38
+ },
39
+ 407: {
40
+ status: 407,
41
+ title: 'Proxy Authentication Required (RFC 7235)',
42
+ type: 'errors/defaults/4xx/proxyauthenticationrequired'
43
+ },
44
+ 408: {
45
+ status: 408,
46
+ title: 'Request Timeout',
47
+ type: 'errors/defaults/4xx/requesttimeout'
48
+ },
49
+ 409: {
50
+ status: 409,
51
+ title: 'Conflict',
52
+ type: 'errors/defaults/4xx/conflict'
53
+ },
54
+ 410: {
55
+ status: 410,
56
+ title: 'Gone',
57
+ type: 'errors/defaults/4xx/gone'
58
+ },
59
+ 411: {
60
+ status: 411,
61
+ title: 'Length Required',
62
+ type: 'errors/defaults/4xx/lengthrequired'
63
+ },
64
+ 412: {
65
+ status: 412,
66
+ title: 'Precondition Failed (RFC 7232)',
67
+ type: 'errors/defaults/4xx/preconditionfailed'
68
+ },
69
+ 413: {
70
+ status: 413,
71
+ title: 'Payload Too Large (RFC 7231)',
72
+ type: 'errors/defaults/4xx/payloadtoolarge'
73
+ },
74
+ 414: {
75
+ status: 414,
76
+ title: 'URI Too Long (RFC 7231)',
77
+ type: 'errors/defaults/4xx/uritoolong'
78
+ },
79
+ 415: {
80
+ status: 415,
81
+ title: 'Unsupported Media Type (RFC 7231)',
82
+ type: 'errors/defaults/4xx/unsupportedmediatype'
83
+ },
84
+ 416: {
85
+ status: 416,
86
+ title: 'Range Not Satisfiable (RFC 7233)',
87
+ type: 'errors/defaults/4xx/rangenotsatisfiable'
88
+ },
89
+ 417: {
90
+ status: 417,
91
+ title: 'Expectation Failed',
92
+ type: 'errors/defaults/4xx/expectationfailed'
93
+ },
94
+ 418: {
95
+ status: 418,
96
+ title: 'I\'m a teapot (RFC 2324, RFC 7168)',
97
+ type: 'errors/defaults/4xx/i\'mateapot'
98
+ },
99
+ 421: {
100
+ status: 421,
101
+ title: 'Misdirected Request (RFC 7540)',
102
+ type: 'errors/defaults/4xx/misdirectedrequest'
103
+ },
104
+ 422: {
105
+ status: 422,
106
+ title: 'Unprocessable Entity (WebDAV; RFC 4918)',
107
+ type: 'errors/defaults/4xx/unprocessableentity'
108
+ },
109
+ 423: {
110
+ status: 423,
111
+ title: 'Locked (WebDAV; RFC 4918)',
112
+ type: 'errors/defaults/4xx/locked'
113
+ },
114
+ 424: {
115
+ status: 424,
116
+ title: 'Failed Dependency (WebDAV; RFC 4918)',
117
+ type: 'errors/defaults/4xx/faileddependency'
118
+ },
119
+ 425: {
120
+ status: 425,
121
+ title: 'Too Early (RFC 8470)',
122
+ type: 'errors/defaults/4xx/tooearly'
123
+ },
124
+ 426: {
125
+ status: 426,
126
+ title: 'Upgrade Required',
127
+ type: 'errors/defaults/4xx/upgraderequired'
128
+ },
129
+ 428: {
130
+ status: 428,
131
+ title: 'Precondition Required (RFC 6585)',
132
+ type: 'errors/defaults/4xx/preconditionrequired'
133
+ },
134
+ 429: {
135
+ status: 429,
136
+ title: 'Too Many Requests (RFC 6585)',
137
+ type: 'errors/defaults/4xx/toomanyrequests'
138
+ },
139
+ 431: {
140
+ status: 431,
141
+ title: 'Request Header Fields Too Large (RFC 6585)',
142
+ type: 'errors/defaults/4xx/requestheaderfieldstoolarge'
143
+ },
144
+ 451: {
145
+ status: 451,
146
+ title: 'Unavailable For Legal Reasons (RFC 7725)',
147
+ type: 'errors/defaults/4xx/unavailableforlegalreasons'
148
+ }
149
+ }
@@ -0,0 +1,59 @@
1
+ // Source: https://en.wikipedia.org/wiki/List_of_HTTP_status_codes
2
+
3
+ export const statusCodes = {
4
+ 500: {
5
+ status: 500,
6
+ title: 'Internal Server Error',
7
+ type: 'errors/defaults/5xx/internalservererror'
8
+ },
9
+ 501: {
10
+ status: 501,
11
+ title: 'Not Implemented',
12
+ type: 'errors/defaults/5xx/notimplemented'
13
+ },
14
+ 502: {
15
+ status: 502,
16
+ title: 'Bad Gateway',
17
+ type: 'errors/defaults/5xx/badgateway'
18
+ },
19
+ 503: {
20
+ status: 503,
21
+ title: 'Service Unavailable',
22
+ type: 'errors/defaults/5xx/serviceunavailable'
23
+ },
24
+ 504: {
25
+ status: 504,
26
+ title: 'Gateway Timeout',
27
+ type: 'errors/defaults/5xx/gatewaytimeout'
28
+ },
29
+ 505: {
30
+ status: 505,
31
+ title: 'HTTP Version Not Supported',
32
+ type: 'errors/defaults/5xx/httpversionnotsupported'
33
+ },
34
+ 506: {
35
+ status: 506,
36
+ title: 'Variant Also Negotiates (RFC 2295)',
37
+ type: 'errors/defaults/5xx/variantalsonegotiates'
38
+ },
39
+ 507: {
40
+ status: 507,
41
+ title: 'Insufficient Storage (WebDAV; RFC 4918)',
42
+ type: 'errors/defaults/5xx/insufficientstorage'
43
+ },
44
+ 508: {
45
+ status: 508,
46
+ title: 'Loop Detected (WebDAV; RFC 5842)',
47
+ type: 'errors/defaults/5xx/loopdetected'
48
+ },
49
+ 511: {
50
+ status: 511,
51
+ title: 'Network Authentication Required (RFC 6585)',
52
+ type: 'errors/defaults/5xx/networkauthenticationrequired'
53
+ },
54
+ 510: {
55
+ status: 510,
56
+ title: 'Not Extended (RFC 2774)',
57
+ type: 'errors/defaults/5xx/notextended'
58
+ }
59
+ }
@@ -0,0 +1,14 @@
1
+ // Source: https://docs.aws.amazon.com/elasticloadbalancing/latest/application/load-balancer-troubleshooting.html
2
+
3
+ export const statusCodes = {
4
+ 460: {
5
+ status: 460,
6
+ title: 'Client closed connection before idle timeout period elapsed',
7
+ type: '/errors/defaults/aws/clientclosedbeforetimeout',
8
+ },
9
+ 463: {
10
+ status: 463,
11
+ title: 'X-Forwarded-For contains more than 30 ips',
12
+ type: '/errors/defaults/aws/toomanyips'
13
+ }
14
+ }
@@ -0,0 +1,50 @@
1
+ // Source: https://support.cloudflare.com/hc/en-us/articles/115003011431
2
+
3
+ export const statusCodes = {
4
+ 520: {
5
+ status: 520,
6
+ title: 'Web Server returned an unknown error',
7
+ type: '/errors/defaults/cloudflare/unknownerror',
8
+ },
9
+ 521: {
10
+ status: 521,
11
+ title: 'Web server is down',
12
+ type: '/errors/defaults/cloudflare/webserverdown',
13
+ },
14
+ 522: {
15
+ status: 522,
16
+ title: 'Connection timed out',
17
+ type: '/errors/defaults/cloudflare/connectiontimeout',
18
+ },
19
+ 523: {
20
+ status: 523,
21
+ title: 'Origin is unreachable',
22
+ type: '/errors/defaults/cloudflare/originunreachable'
23
+ },
24
+ 524: {
25
+ status: 524,
26
+ title: 'A timeout occurred',
27
+ type: '/errors/defaults/cloudflare/timeout'
28
+ },
29
+ 525: {
30
+ status: 525,
31
+ title: 'SSL handshake failed',
32
+ type: '/errors/defaults/cloudflare/sslhandshakefail'
33
+ },
34
+ 526: {
35
+ status: 526,
36
+ title: 'Invalid SSL certificate',
37
+ type: '/errors/defaults/cloudflare/invalidssl'
38
+ },
39
+ // A 527 error indicates an interrupted connection between Cloudflare and your origin's Railgun server (rg-listener)
40
+ 527: {
41
+ status: 527,
42
+ title: 'Railgun Listener to origin error',
43
+ type: '/errors/defaults/cloudflare/railgunoriginerr'
44
+ },
45
+ 530: {
46
+ status: 530,
47
+ title: 'Cloudflare returned 1xxx error',
48
+ type: '/errors/defaults/cloudflare/1xxx'
49
+ }
50
+ }
@@ -0,0 +1,19 @@
1
+ // Source: https://en.wikipedia.org/wiki/List_of_HTTP_status_codes
2
+
3
+ export const statusCodes = {
4
+ 440: {
5
+ status: 440,
6
+ title: 'Login Time-out',
7
+ type: '/errors/defaults/iis/logintimeout',
8
+ },
9
+ 449: {
10
+ status: 449,
11
+ title: 'Retry with required information',
12
+ type: '/errors/defaults/iis/retrywith'
13
+ },
14
+ 451: {
15
+ status: 451,
16
+ title: 'Redirect',
17
+ type: '/errors/defaults/iis/redirect'
18
+ }
19
+ }
@@ -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'
@@ -0,0 +1,34 @@
1
+ // Source: https://en.wikipedia.org/wiki/List_of_HTTP_status_codes
2
+
3
+ export const statusCodes = {
4
+ 444: {
5
+ status: 494,
6
+ title: 'No Response',
7
+ type: '/errors/defaults/nginx/noresponse',
8
+ },
9
+ 494: {
10
+ status: 494,
11
+ title: 'Request header too large',
12
+ type: '/errors/defaults/nginx/headertoolarge',
13
+ },
14
+ 495: {
15
+ status: 495,
16
+ title: 'SSL Certificate Error',
17
+ type: '/errors/defaults/nginx/sslcerterr'
18
+ },
19
+ 496: {
20
+ status: 496,
21
+ title: 'SSL Certificate Required',
22
+ type: '/errors/defaults/nginx/sslcertrequired'
23
+ },
24
+ 497: {
25
+ status: 497,
26
+ title: 'HTTP Request Sent to HTTPS Port',
27
+ type: '/errors/defaults/nginx/httpreqtohttpsport'
28
+ },
29
+ 499: {
30
+ status: 499,
31
+ title: 'Client Closed Request',
32
+ type: '/errors/defaults/nginx/clientclosedreq'
33
+ }
34
+ }
@@ -0,0 +1,37 @@
1
+ export const otherErrors = {
2
+ 'unknown': {
3
+ status: 500,
4
+ title: 'Unknown error',
5
+ type: '/errors/defaults/others/unknown',
6
+ },
7
+ 'corsError': {
8
+ status: 401,
9
+ title: 'Request not allowed by CORS policy',
10
+ type: '/errors/defaults/others/corsdenied',
11
+ },
12
+ 'problemParseError': {
13
+ status: 1000,
14
+ title: 'Could not parse problem details',
15
+ type: '/errors/defaults/others/problemparseerror',
16
+ },
17
+ 'problemJsonError': {
18
+ status: 1000,
19
+ title: 'Could not parse problem JSON',
20
+ type: '/errors/defaults/others/problemjsonerror',
21
+ },
22
+ 'notAnError': {
23
+ status: 1000,
24
+ title: 'Not an error',
25
+ type: '/errors/defaults/others/notanerror',
26
+ },
27
+ 'networkError': {
28
+ status: 1000,
29
+ title: 'Network error',
30
+ type: '/errors/defaults/others/networkerror'
31
+ },
32
+ 'inputValidationError': {
33
+ status: 400,
34
+ title: 'Input failed validation',
35
+ type: '/errors/defaults/others/validationerror'
36
+ }
37
+ }
package/src/index.ts ADDED
@@ -0,0 +1,4 @@
1
+ export { Problem } from './problem.js'
2
+ export * as defaults from './defaults/index.js'
3
+ export * from './typings/index.js'
4
+ export * from './util/isProblemArray.js'
@@ -0,0 +1,28 @@
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
+ }
@@ -0,0 +1,90 @@
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: Problem[] = []
32
+
33
+ const prob = getProblems(input)
34
+
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)
54
+ }
55
+
56
+ for (const p of problems) {
57
+ if (!p.middleware) p.middleware = this.name
58
+ }
59
+
60
+ return problems
61
+ }
62
+
63
+ fallback: ProblemOpts = otherErrors.unknown
64
+
65
+ problemListeners: ProblemListener[] = []
66
+
67
+ protected emit = (list: Problem[]) => {
68
+ for (const listener of this.problemListeners) {
69
+ listener(list)
70
+ }
71
+ }
72
+
73
+ /**
74
+ * Listen to problems
75
+ */
76
+ onProblem = (listener: ProblemListener) => {
77
+ this.problemListeners.push(listener)
78
+ }
79
+
80
+ /**
81
+ * Un-listen to problems
82
+ */
83
+ offProblem = (listener: ProblemListener) => {
84
+ const idx = this.problemListeners.findIndex(val => val === listener)
85
+
86
+ if (idx < 0 || isNaN(idx)) return
87
+
88
+ this.problemListeners.splice(idx, 1)
89
+ }
90
+ }
@@ -0,0 +1,71 @@
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
+ }
@@ -0,0 +1,61 @@
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
+
13
+ const parse: Parser = (input) => {
14
+ if (typeof input !== 'object' || input == null) return []
15
+ if (!isAxiosError(input)) return []
16
+ const request = input.request
17
+
18
+ if (input?.response?.data) {
19
+ const problems = getProblems(input?.response?.data)
20
+ if (problems?.length) return problems
21
+ }
22
+
23
+ if (input?.response) {
24
+ const opts = getHttpError(input.response.status)
25
+ if (opts) return [new Problem({
26
+ ...opts,
27
+ 'instance': input?.config?.url,
28
+ 'stack': input?.stack,
29
+ 'errorObject': input
30
+ })]
31
+ }
32
+
33
+ // TODO: Fix this, this seems bad
34
+ if (request) {
35
+ const isNode = typeof process !== 'undefined' && process.versions && process.versions.node
36
+ const isBrowser = typeof window !== 'undefined' && typeof window.document !== 'undefined'
37
+
38
+ if (isBrowser && request instanceof XMLHttpRequest) {
39
+ return [new Problem({
40
+ ...otherErrors.networkError,
41
+ 'instance': input?.config?.url,
42
+ 'stack': input?.stack,
43
+ 'errorObject': input
44
+ })]
45
+ }
46
+
47
+ // Probably a ClientRequest
48
+ if (isNode && 'pipe' in request && 'destroy' in request) {
49
+ return [new Problem({
50
+ ...otherErrors.networkError,
51
+ 'instance': input?.config?.url,
52
+ 'stack': input?.stack,
53
+ 'errorObject': input
54
+ })]
55
+ }
56
+ }
57
+
58
+ return []
59
+ }
60
+
61
+ export default parse