pino-std 0.0.0-stage → 7.2.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/.editorconfig +13 -0
- package/.github/dependabot.yml +13 -0
- package/.github/workflows/ci.yml +81 -0
- package/LICENSE +7 -0
- package/Readme.md +182 -0
- package/eslint.config.js +7 -0
- package/index.d.ts +145 -0
- package/index.js +36 -0
- package/lib/contract.js +8 -0
- package/lib/err-helpers.js +118 -0
- package/lib/err-proto.js +48 -0
- package/lib/err-with-cause.js +48 -0
- package/lib/err.js +45 -0
- package/lib/req.js +100 -0
- package/lib/res.js +47 -0
- package/package.json +40 -4
- package/test/err-with-cause.test.js +187 -0
- package/test/err.test.js +200 -0
- package/test/req.test.js +477 -0
- package/test/res.test.js +120 -0
- package/test/types/index.test-d.ts +71 -0
- package/tsconfig.json +13 -0
- package/README.md +0 -3
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
module.exports = errWithCauseSerializer
|
|
4
|
+
|
|
5
|
+
const { isErrorLike } = require('./err-helpers')
|
|
6
|
+
const { pinoErrProto, pinoErrorSymbols } = require('./err-proto')
|
|
7
|
+
const { seen } = pinoErrorSymbols
|
|
8
|
+
|
|
9
|
+
const { toString } = Object.prototype
|
|
10
|
+
|
|
11
|
+
function errWithCauseSerializer (err) {
|
|
12
|
+
if (!isErrorLike(err)) {
|
|
13
|
+
return err
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
err[seen] = undefined // tag to prevent re-looking at this
|
|
17
|
+
const _err = Object.create(pinoErrProto)
|
|
18
|
+
_err.type = toString.call(err.constructor) === '[object Function]'
|
|
19
|
+
? err.constructor.name
|
|
20
|
+
: err.name
|
|
21
|
+
_err.message = err.message
|
|
22
|
+
_err.stack = err.stack
|
|
23
|
+
|
|
24
|
+
if (Array.isArray(err.errors)) {
|
|
25
|
+
_err.aggregateErrors = err.errors.map(err => errWithCauseSerializer(err))
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (isErrorLike(err.cause) && !Object.prototype.hasOwnProperty.call(err.cause, seen)) {
|
|
29
|
+
_err.cause = errWithCauseSerializer(err.cause)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
for (const key in err) {
|
|
33
|
+
if (_err[key] === undefined) {
|
|
34
|
+
const val = err[key]
|
|
35
|
+
if (isErrorLike(val)) {
|
|
36
|
+
if (!Object.prototype.hasOwnProperty.call(val, seen)) {
|
|
37
|
+
_err[key] = errWithCauseSerializer(val)
|
|
38
|
+
}
|
|
39
|
+
} else {
|
|
40
|
+
_err[key] = val
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
delete err[seen] // clean up tag in case err is serialized again later
|
|
46
|
+
_err.raw = err
|
|
47
|
+
return _err
|
|
48
|
+
}
|
package/lib/err.js
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
module.exports = errSerializer
|
|
4
|
+
|
|
5
|
+
const { messageWithCauses, stackWithCauses, isErrorLike } = require('./err-helpers')
|
|
6
|
+
const { pinoErrProto, pinoErrorSymbols } = require('./err-proto')
|
|
7
|
+
const { seen } = pinoErrorSymbols
|
|
8
|
+
|
|
9
|
+
const { toString } = Object.prototype
|
|
10
|
+
|
|
11
|
+
function errSerializer (err) {
|
|
12
|
+
if (!isErrorLike(err)) {
|
|
13
|
+
return err
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
err[seen] = undefined // tag to prevent re-looking at this
|
|
17
|
+
const _err = Object.create(pinoErrProto)
|
|
18
|
+
_err.type = toString.call(err.constructor) === '[object Function]'
|
|
19
|
+
? err.constructor.name
|
|
20
|
+
: err.name
|
|
21
|
+
_err.message = messageWithCauses(err)
|
|
22
|
+
_err.stack = stackWithCauses(err)
|
|
23
|
+
|
|
24
|
+
if (Array.isArray(err.errors)) {
|
|
25
|
+
_err.aggregateErrors = err.errors.map(err => errSerializer(err))
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
for (const key in err) {
|
|
29
|
+
if (_err[key] === undefined) {
|
|
30
|
+
const val = err[key]
|
|
31
|
+
if (isErrorLike(val)) {
|
|
32
|
+
// We append cause messages and stacks to _err, therefore skipping causes here
|
|
33
|
+
if (key !== 'cause' && !Object.prototype.hasOwnProperty.call(val, seen)) {
|
|
34
|
+
_err[key] = errSerializer(val)
|
|
35
|
+
}
|
|
36
|
+
} else {
|
|
37
|
+
_err[key] = val
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
delete err[seen] // clean up tag in case err is serialized again later
|
|
43
|
+
_err.raw = err
|
|
44
|
+
return _err
|
|
45
|
+
}
|
package/lib/req.js
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
module.exports = {
|
|
4
|
+
mapHttpRequest,
|
|
5
|
+
reqSerializer
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const rawSymbol = Symbol('pino-raw-req-ref')
|
|
9
|
+
const pinoReqProto = Object.create({}, {
|
|
10
|
+
id: {
|
|
11
|
+
enumerable: true,
|
|
12
|
+
writable: true,
|
|
13
|
+
value: ''
|
|
14
|
+
},
|
|
15
|
+
method: {
|
|
16
|
+
enumerable: true,
|
|
17
|
+
writable: true,
|
|
18
|
+
value: ''
|
|
19
|
+
},
|
|
20
|
+
url: {
|
|
21
|
+
enumerable: true,
|
|
22
|
+
writable: true,
|
|
23
|
+
value: ''
|
|
24
|
+
},
|
|
25
|
+
query: {
|
|
26
|
+
enumerable: true,
|
|
27
|
+
writable: true,
|
|
28
|
+
value: ''
|
|
29
|
+
},
|
|
30
|
+
params: {
|
|
31
|
+
enumerable: true,
|
|
32
|
+
writable: true,
|
|
33
|
+
value: ''
|
|
34
|
+
},
|
|
35
|
+
headers: {
|
|
36
|
+
enumerable: true,
|
|
37
|
+
writable: true,
|
|
38
|
+
value: {}
|
|
39
|
+
},
|
|
40
|
+
remoteAddress: {
|
|
41
|
+
enumerable: true,
|
|
42
|
+
writable: true,
|
|
43
|
+
value: ''
|
|
44
|
+
},
|
|
45
|
+
remotePort: {
|
|
46
|
+
enumerable: true,
|
|
47
|
+
writable: true,
|
|
48
|
+
value: ''
|
|
49
|
+
},
|
|
50
|
+
raw: {
|
|
51
|
+
enumerable: false,
|
|
52
|
+
get: function () {
|
|
53
|
+
return this[rawSymbol]
|
|
54
|
+
},
|
|
55
|
+
set: function (val) {
|
|
56
|
+
this[rawSymbol] = val
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
})
|
|
60
|
+
Object.defineProperty(pinoReqProto, rawSymbol, {
|
|
61
|
+
writable: true,
|
|
62
|
+
value: {}
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
function reqSerializer (req) {
|
|
66
|
+
// req.info is for hapi compat.
|
|
67
|
+
const connection = req.info || req.socket
|
|
68
|
+
const _req = Object.create(pinoReqProto)
|
|
69
|
+
_req.id = (typeof req.id === 'function' ? req.id() : (req.id || (req.info ? req.info.id : undefined)))
|
|
70
|
+
_req.method = req.method
|
|
71
|
+
// req.originalUrl is for expressjs compat.
|
|
72
|
+
if (req.originalUrl) {
|
|
73
|
+
_req.url = req.originalUrl
|
|
74
|
+
} else {
|
|
75
|
+
const path = req.path
|
|
76
|
+
// path for safe hapi compat.
|
|
77
|
+
_req.url = typeof path === 'string' ? path : (req.url ? req.url.path || req.url : undefined)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (req.query) {
|
|
81
|
+
_req.query = req.query
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (req.params) {
|
|
85
|
+
_req.params = req.params
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
_req.headers = req.headers
|
|
89
|
+
_req.remoteAddress = connection && connection.remoteAddress
|
|
90
|
+
_req.remotePort = connection && connection.remotePort
|
|
91
|
+
// req.raw is for hapi compat/equivalence
|
|
92
|
+
_req.raw = req.raw || req
|
|
93
|
+
return _req
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function mapHttpRequest (req) {
|
|
97
|
+
return {
|
|
98
|
+
req: reqSerializer(req)
|
|
99
|
+
}
|
|
100
|
+
}
|
package/lib/res.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
module.exports = {
|
|
4
|
+
mapHttpResponse,
|
|
5
|
+
resSerializer
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const rawSymbol = Symbol('pino-raw-res-ref')
|
|
9
|
+
const pinoResProto = Object.create({}, {
|
|
10
|
+
statusCode: {
|
|
11
|
+
enumerable: true,
|
|
12
|
+
writable: true,
|
|
13
|
+
value: 0
|
|
14
|
+
},
|
|
15
|
+
headers: {
|
|
16
|
+
enumerable: true,
|
|
17
|
+
writable: true,
|
|
18
|
+
value: ''
|
|
19
|
+
},
|
|
20
|
+
raw: {
|
|
21
|
+
enumerable: false,
|
|
22
|
+
get: function () {
|
|
23
|
+
return this[rawSymbol]
|
|
24
|
+
},
|
|
25
|
+
set: function (val) {
|
|
26
|
+
this[rawSymbol] = val
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
})
|
|
30
|
+
Object.defineProperty(pinoResProto, rawSymbol, {
|
|
31
|
+
writable: true,
|
|
32
|
+
value: {}
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
function resSerializer (res) {
|
|
36
|
+
const _res = Object.create(pinoResProto)
|
|
37
|
+
_res.statusCode = res.headersSent ? res.statusCode : null
|
|
38
|
+
_res.headers = res.getHeaders ? res.getHeaders() : res._headers
|
|
39
|
+
_res.raw = res
|
|
40
|
+
return _res
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function mapHttpResponse (res) {
|
|
44
|
+
return {
|
|
45
|
+
res: resSerializer(res)
|
|
46
|
+
}
|
|
47
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,42 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pino-std",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "7.2.0",
|
|
4
|
+
"description": "A collection of standard object serializers for Pino",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"type": "commonjs",
|
|
7
|
+
"types": "index.d.ts",
|
|
8
|
+
"scripts": {
|
|
9
|
+
"lint": "eslint .",
|
|
10
|
+
"lint:fix": "eslint --fix .",
|
|
11
|
+
"lint-ci": "eslint --max-warnings=0 .",
|
|
12
|
+
"test": "borp -p 'test/**/*.js'",
|
|
13
|
+
"test-ci": "borp --coverage -p 'test/**/*.js'",
|
|
14
|
+
"test-types": "tsc && tsd"
|
|
15
|
+
},
|
|
16
|
+
"repository": {
|
|
17
|
+
"type": "git",
|
|
18
|
+
"url": "git+ssh://git@github.com/pinojs/pino-std-serializers.git"
|
|
19
|
+
},
|
|
20
|
+
"keywords": [
|
|
21
|
+
"pino",
|
|
22
|
+
"logging"
|
|
23
|
+
],
|
|
24
|
+
"author": "James Sumners <james.sumners@gmail.com>",
|
|
25
|
+
"license": "MIT",
|
|
26
|
+
"bugs": {
|
|
27
|
+
"url": "https://github.com/pinojs/pino-std-serializers/issues"
|
|
28
|
+
},
|
|
29
|
+
"homepage": "https://github.com/pinojs/pino-std-serializers#readme",
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"@matteo.collina/tspl": "^0.2.0",
|
|
32
|
+
"@types/node": "^25.0.3",
|
|
33
|
+
"borp": "^0.21.0",
|
|
34
|
+
"eslint": "^9.39.2",
|
|
35
|
+
"neostandard": "^0.12.2",
|
|
36
|
+
"tsd": "^0.33.0",
|
|
37
|
+
"typescript": "~5.9.3"
|
|
38
|
+
},
|
|
39
|
+
"tsd": {
|
|
40
|
+
"directory": "test/types"
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { test } = require('node:test')
|
|
4
|
+
const assert = require('node:assert')
|
|
5
|
+
const serializer = require('../lib/err-with-cause')
|
|
6
|
+
const { wrapErrorSerializer } = require('../')
|
|
7
|
+
|
|
8
|
+
test('serializes Error objects', () => {
|
|
9
|
+
const serialized = serializer(Error('foo'))
|
|
10
|
+
assert.strictEqual(serialized.type, 'Error')
|
|
11
|
+
assert.strictEqual(serialized.message, 'foo')
|
|
12
|
+
assert.match(serialized.stack, /err-with-cause\.test\.js:/)
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
test('serializes Error objects with extra properties', () => {
|
|
16
|
+
const err = Error('foo')
|
|
17
|
+
err.statusCode = 500
|
|
18
|
+
const serialized = serializer(err)
|
|
19
|
+
assert.strictEqual(serialized.type, 'Error')
|
|
20
|
+
assert.strictEqual(serialized.message, 'foo')
|
|
21
|
+
assert.ok(serialized.statusCode)
|
|
22
|
+
assert.strictEqual(serialized.statusCode, 500)
|
|
23
|
+
assert.match(serialized.stack, /err-with-cause\.test\.js:/)
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
test('serializes Error objects with subclass "type"', () => {
|
|
27
|
+
class MyError extends Error {}
|
|
28
|
+
|
|
29
|
+
const err = new MyError('foo')
|
|
30
|
+
const serialized = serializer(err)
|
|
31
|
+
assert.strictEqual(serialized.type, 'MyError')
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
test('serializes nested errors', () => {
|
|
35
|
+
const err = Error('foo')
|
|
36
|
+
err.inner = Error('bar')
|
|
37
|
+
const serialized = serializer(err)
|
|
38
|
+
assert.strictEqual(serialized.type, 'Error')
|
|
39
|
+
assert.strictEqual(serialized.message, 'foo')
|
|
40
|
+
assert.match(serialized.stack, /err-with-cause\.test\.js:/)
|
|
41
|
+
assert.strictEqual(serialized.inner.type, 'Error')
|
|
42
|
+
assert.strictEqual(serialized.inner.message, 'bar')
|
|
43
|
+
assert.match(serialized.inner.stack, /Error: bar/)
|
|
44
|
+
assert.match(serialized.inner.stack, /err-with-cause\.test\.js:/)
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
test('serializes error causes', () => {
|
|
48
|
+
const innerErr = Error('inner')
|
|
49
|
+
const middleErr = Error('middle')
|
|
50
|
+
middleErr.cause = innerErr
|
|
51
|
+
const outerErr = Error('outer')
|
|
52
|
+
outerErr.cause = middleErr
|
|
53
|
+
|
|
54
|
+
const serialized = serializer(outerErr)
|
|
55
|
+
|
|
56
|
+
assert.strictEqual(serialized.type, 'Error')
|
|
57
|
+
assert.strictEqual(serialized.message, 'outer')
|
|
58
|
+
assert.match(serialized.stack, /err-with-cause\.test\.js:/)
|
|
59
|
+
|
|
60
|
+
assert.strictEqual(serialized.cause.type, 'Error')
|
|
61
|
+
assert.strictEqual(serialized.cause.message, 'middle')
|
|
62
|
+
assert.match(serialized.cause.stack, /err-with-cause\.test\.js:/)
|
|
63
|
+
|
|
64
|
+
assert.strictEqual(serialized.cause.cause.type, 'Error')
|
|
65
|
+
assert.strictEqual(serialized.cause.cause.message, 'inner')
|
|
66
|
+
assert.match(serialized.cause.cause.stack, /err-with-cause\.test\.js:/)
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
test('keeps non-error cause', () => {
|
|
70
|
+
const err = Error('foo')
|
|
71
|
+
err.cause = 'abc'
|
|
72
|
+
const serialized = serializer(err)
|
|
73
|
+
assert.strictEqual(serialized.type, 'Error')
|
|
74
|
+
assert.strictEqual(serialized.message, 'foo')
|
|
75
|
+
assert.strictEqual(serialized.cause, 'abc')
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
test('prevents infinite recursion', () => {
|
|
79
|
+
const err = Error('foo')
|
|
80
|
+
err.inner = err
|
|
81
|
+
const serialized = serializer(err)
|
|
82
|
+
assert.strictEqual(serialized.type, 'Error')
|
|
83
|
+
assert.strictEqual(serialized.message, 'foo')
|
|
84
|
+
assert.match(serialized.stack, /err-with-cause\.test\.js:/)
|
|
85
|
+
assert.ok(!serialized.inner)
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
test('cleans up infinite recursion tracking', () => {
|
|
89
|
+
const err = Error('foo')
|
|
90
|
+
const bar = Error('bar')
|
|
91
|
+
err.inner = bar
|
|
92
|
+
bar.inner = err
|
|
93
|
+
|
|
94
|
+
serializer(err)
|
|
95
|
+
const serialized = serializer(err)
|
|
96
|
+
|
|
97
|
+
assert.strictEqual(serialized.type, 'Error')
|
|
98
|
+
assert.strictEqual(serialized.message, 'foo')
|
|
99
|
+
assert.match(serialized.stack, /err-with-cause\.test\.js:/)
|
|
100
|
+
assert.ok(serialized.inner)
|
|
101
|
+
assert.strictEqual(serialized.inner.type, 'Error')
|
|
102
|
+
assert.strictEqual(serialized.inner.message, 'bar')
|
|
103
|
+
assert.match(serialized.inner.stack, /Error: bar/)
|
|
104
|
+
assert.ok(!serialized.inner.inner)
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
test('err.raw is available', () => {
|
|
108
|
+
const err = Error('foo')
|
|
109
|
+
const serialized = serializer(err)
|
|
110
|
+
assert.strictEqual(serialized.raw, err)
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
test('redefined err.constructor doesnt crash serializer', () => {
|
|
114
|
+
function check (a, name) {
|
|
115
|
+
assert.strictEqual(a.type, name)
|
|
116
|
+
assert.strictEqual(a.message, 'foo')
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const err1 = TypeError('foo')
|
|
120
|
+
err1.constructor = '10'
|
|
121
|
+
|
|
122
|
+
const err2 = TypeError('foo')
|
|
123
|
+
err2.constructor = undefined
|
|
124
|
+
|
|
125
|
+
const err3 = Error('foo')
|
|
126
|
+
err3.constructor = null
|
|
127
|
+
|
|
128
|
+
const err4 = Error('foo')
|
|
129
|
+
err4.constructor = 10
|
|
130
|
+
|
|
131
|
+
class MyError extends Error {}
|
|
132
|
+
|
|
133
|
+
const err5 = new MyError('foo')
|
|
134
|
+
err5.constructor = undefined
|
|
135
|
+
|
|
136
|
+
check(serializer(err1), 'TypeError')
|
|
137
|
+
check(serializer(err2), 'TypeError')
|
|
138
|
+
check(serializer(err3), 'Error')
|
|
139
|
+
check(serializer(err4), 'Error')
|
|
140
|
+
// We do not expect 'MyError' because err5.constructor has been blown away.
|
|
141
|
+
// `err5.name` is 'Error' from the base class prototype.
|
|
142
|
+
check(serializer(err5), 'Error')
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
test('pass through anything that does not look like an Error', () => {
|
|
146
|
+
function check (a) {
|
|
147
|
+
assert.strictEqual(serializer(a), a)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
check('foo')
|
|
151
|
+
check({ hello: 'world' })
|
|
152
|
+
check([1, 2])
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
test('can wrap err serializers', () => {
|
|
156
|
+
const err = Error('foo')
|
|
157
|
+
err.foo = 'foo'
|
|
158
|
+
const serializer = wrapErrorSerializer(function (err) {
|
|
159
|
+
delete err.foo
|
|
160
|
+
err.bar = 'bar'
|
|
161
|
+
return err
|
|
162
|
+
})
|
|
163
|
+
const serialized = serializer(err)
|
|
164
|
+
assert.strictEqual(serialized.type, 'Error')
|
|
165
|
+
assert.strictEqual(serialized.message, 'foo')
|
|
166
|
+
assert.match(serialized.stack, /err-with-cause\.test\.js:/)
|
|
167
|
+
assert.ok(!serialized.foo)
|
|
168
|
+
assert.strictEqual(serialized.bar, 'bar')
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
test('serializes aggregate errors', { skip: !global.AggregateError }, () => {
|
|
172
|
+
const foo = new Error('foo')
|
|
173
|
+
const bar = new Error('bar')
|
|
174
|
+
for (const aggregate of [
|
|
175
|
+
new AggregateError([foo, bar], 'aggregated message'),
|
|
176
|
+
{ errors: [foo, bar], message: 'aggregated message', stack: 'err-with-cause.test.js:' }
|
|
177
|
+
]) {
|
|
178
|
+
const serialized = serializer(aggregate)
|
|
179
|
+
assert.strictEqual(serialized.message, 'aggregated message')
|
|
180
|
+
assert.strictEqual(serialized.aggregateErrors.length, 2)
|
|
181
|
+
assert.strictEqual(serialized.aggregateErrors[0].message, 'foo')
|
|
182
|
+
assert.strictEqual(serialized.aggregateErrors[1].message, 'bar')
|
|
183
|
+
assert.match(serialized.aggregateErrors[0].stack, /^Error: foo/)
|
|
184
|
+
assert.match(serialized.aggregateErrors[1].stack, /^Error: bar/)
|
|
185
|
+
assert.match(serialized.stack, /err-with-cause\.test\.js:/)
|
|
186
|
+
}
|
|
187
|
+
})
|
package/test/err.test.js
ADDED
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const assert = require('node:assert')
|
|
4
|
+
const { test } = require('node:test')
|
|
5
|
+
const serializer = require('../lib/err')
|
|
6
|
+
const { wrapErrorSerializer } = require('../')
|
|
7
|
+
|
|
8
|
+
test('serializes Error objects', () => {
|
|
9
|
+
const serialized = serializer(Error('foo'))
|
|
10
|
+
assert.strictEqual(serialized.type, 'Error')
|
|
11
|
+
assert.strictEqual(serialized.message, 'foo')
|
|
12
|
+
assert.match(serialized.stack, /err\.test\.js:/)
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
test('serializes Error objects with extra properties', () => {
|
|
16
|
+
const err = Error('foo')
|
|
17
|
+
err.statusCode = 500
|
|
18
|
+
const serialized = serializer(err)
|
|
19
|
+
assert.strictEqual(serialized.type, 'Error')
|
|
20
|
+
assert.strictEqual(serialized.message, 'foo')
|
|
21
|
+
assert.ok(serialized.statusCode)
|
|
22
|
+
assert.strictEqual(serialized.statusCode, 500)
|
|
23
|
+
assert.match(serialized.stack, /err\.test\.js:/)
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
test('serializes Error objects with subclass "type"', () => {
|
|
27
|
+
class MyError extends Error {}
|
|
28
|
+
const err = new MyError('foo')
|
|
29
|
+
const serialized = serializer(err)
|
|
30
|
+
assert.strictEqual(serialized.type, 'MyError')
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
test('serializes nested errors', () => {
|
|
34
|
+
const err = Error('foo')
|
|
35
|
+
err.inner = Error('bar')
|
|
36
|
+
const serialized = serializer(err)
|
|
37
|
+
assert.strictEqual(serialized.type, 'Error')
|
|
38
|
+
assert.strictEqual(serialized.message, 'foo')
|
|
39
|
+
assert.match(serialized.stack, /err\.test\.js:/)
|
|
40
|
+
assert.strictEqual(serialized.inner.type, 'Error')
|
|
41
|
+
assert.strictEqual(serialized.inner.message, 'bar')
|
|
42
|
+
assert.match(serialized.inner.stack, /Error: bar/)
|
|
43
|
+
assert.match(serialized.inner.stack, /err\.test\.js:/)
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
test('serializes error causes', () => {
|
|
47
|
+
for (const cause of [
|
|
48
|
+
Error('bar'),
|
|
49
|
+
{ message: 'bar', stack: 'Error: bar: err.test.js:' }
|
|
50
|
+
]) {
|
|
51
|
+
const err = Error('foo')
|
|
52
|
+
err.cause = cause
|
|
53
|
+
err.cause.cause = Error('abc')
|
|
54
|
+
const serialized = serializer(err)
|
|
55
|
+
assert.strictEqual(serialized.type, 'Error')
|
|
56
|
+
assert.strictEqual(serialized.message, 'foo: bar: abc')
|
|
57
|
+
assert.match(serialized.stack, /err\.test\.js:/)
|
|
58
|
+
assert.match(serialized.stack, /Error: foo/)
|
|
59
|
+
assert.match(serialized.stack, /Error: bar/)
|
|
60
|
+
assert.match(serialized.stack, /Error: abc/)
|
|
61
|
+
assert.ok(!serialized.cause)
|
|
62
|
+
}
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
test('serializes error causes with VError support', function (t) {
|
|
66
|
+
// Fake VError-style setup
|
|
67
|
+
const err = Error('foo: bar')
|
|
68
|
+
err.foo = 'abc'
|
|
69
|
+
err.cause = function () {
|
|
70
|
+
const err = Error('bar')
|
|
71
|
+
err.cause = Error(this.foo)
|
|
72
|
+
return err
|
|
73
|
+
}
|
|
74
|
+
const serialized = serializer(err)
|
|
75
|
+
assert.strictEqual(serialized.type, 'Error')
|
|
76
|
+
assert.strictEqual(serialized.message, 'foo: bar: abc')
|
|
77
|
+
assert.match(serialized.stack, /err\.test\.js:/)
|
|
78
|
+
assert.match(serialized.stack, /Error: foo/)
|
|
79
|
+
assert.match(serialized.stack, /Error: bar/)
|
|
80
|
+
assert.match(serialized.stack, /Error: abc/)
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
test('keeps non-error cause', () => {
|
|
84
|
+
const err = Error('foo')
|
|
85
|
+
err.cause = 'abc'
|
|
86
|
+
const serialized = serializer(err)
|
|
87
|
+
assert.strictEqual(serialized.type, 'Error')
|
|
88
|
+
assert.strictEqual(serialized.message, 'foo')
|
|
89
|
+
assert.strictEqual(serialized.cause, 'abc')
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
test('prevents infinite recursion', () => {
|
|
93
|
+
const err = Error('foo')
|
|
94
|
+
err.inner = err
|
|
95
|
+
const serialized = serializer(err)
|
|
96
|
+
assert.strictEqual(serialized.type, 'Error')
|
|
97
|
+
assert.strictEqual(serialized.message, 'foo')
|
|
98
|
+
assert.match(serialized.stack, /err\.test\.js:/)
|
|
99
|
+
assert.ok(!serialized.inner)
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
test('cleans up infinite recursion tracking', () => {
|
|
103
|
+
const err = Error('foo')
|
|
104
|
+
const bar = Error('bar')
|
|
105
|
+
err.inner = bar
|
|
106
|
+
bar.inner = err
|
|
107
|
+
|
|
108
|
+
serializer(err)
|
|
109
|
+
const serialized = serializer(err)
|
|
110
|
+
|
|
111
|
+
assert.strictEqual(serialized.type, 'Error')
|
|
112
|
+
assert.strictEqual(serialized.message, 'foo')
|
|
113
|
+
assert.match(serialized.stack, /err\.test\.js:/)
|
|
114
|
+
assert.ok(serialized.inner)
|
|
115
|
+
assert.strictEqual(serialized.inner.type, 'Error')
|
|
116
|
+
assert.strictEqual(serialized.inner.message, 'bar')
|
|
117
|
+
assert.match(serialized.inner.stack, /Error: bar/)
|
|
118
|
+
assert.ok(!serialized.inner.inner)
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
test('err.raw is available', () => {
|
|
122
|
+
const err = Error('foo')
|
|
123
|
+
const serialized = serializer(err)
|
|
124
|
+
assert.strictEqual(serialized.raw, err)
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
test('redefined err.constructor doesnt crash serializer', () => {
|
|
128
|
+
function check (a, name) {
|
|
129
|
+
assert.strictEqual(a.type, name)
|
|
130
|
+
assert.strictEqual(a.message, 'foo')
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const err1 = TypeError('foo')
|
|
134
|
+
err1.constructor = '10'
|
|
135
|
+
|
|
136
|
+
const err2 = TypeError('foo')
|
|
137
|
+
err2.constructor = undefined
|
|
138
|
+
|
|
139
|
+
const err3 = Error('foo')
|
|
140
|
+
err3.constructor = null
|
|
141
|
+
|
|
142
|
+
const err4 = Error('foo')
|
|
143
|
+
err4.constructor = 10
|
|
144
|
+
|
|
145
|
+
class MyError extends Error {}
|
|
146
|
+
const err5 = new MyError('foo')
|
|
147
|
+
err5.constructor = undefined
|
|
148
|
+
|
|
149
|
+
check(serializer(err1), 'TypeError')
|
|
150
|
+
check(serializer(err2), 'TypeError')
|
|
151
|
+
check(serializer(err3), 'Error')
|
|
152
|
+
check(serializer(err4), 'Error')
|
|
153
|
+
// We do not expect 'MyError' because err5.constructor has been blown away.
|
|
154
|
+
// `err5.name` is 'Error' from the base class prototype.
|
|
155
|
+
check(serializer(err5), 'Error')
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
test('pass through anything that does not look like an Error', () => {
|
|
159
|
+
function check (a) {
|
|
160
|
+
assert.strictEqual(serializer(a), a)
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
check('foo')
|
|
164
|
+
check({ hello: 'world' })
|
|
165
|
+
check([1, 2])
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
test('can wrap err serializers', () => {
|
|
169
|
+
const err = Error('foo')
|
|
170
|
+
err.foo = 'foo'
|
|
171
|
+
const serializer = wrapErrorSerializer(function (err) {
|
|
172
|
+
delete err.foo
|
|
173
|
+
err.bar = 'bar'
|
|
174
|
+
return err
|
|
175
|
+
})
|
|
176
|
+
const serialized = serializer(err)
|
|
177
|
+
assert.strictEqual(serialized.type, 'Error')
|
|
178
|
+
assert.strictEqual(serialized.message, 'foo')
|
|
179
|
+
assert.match(serialized.stack, /err\.test\.js:/)
|
|
180
|
+
assert.ok(!serialized.foo)
|
|
181
|
+
assert.strictEqual(serialized.bar, 'bar')
|
|
182
|
+
})
|
|
183
|
+
|
|
184
|
+
test('serializes aggregate errors', { skip: !global.AggregateError }, () => {
|
|
185
|
+
const foo = new Error('foo')
|
|
186
|
+
const bar = new Error('bar')
|
|
187
|
+
for (const aggregate of [
|
|
188
|
+
new AggregateError([foo, bar], 'aggregated message'),
|
|
189
|
+
{ errors: [foo, bar], message: 'aggregated message', stack: 'err.test.js:' }
|
|
190
|
+
]) {
|
|
191
|
+
const serialized = serializer(aggregate)
|
|
192
|
+
assert.strictEqual(serialized.message, 'aggregated message')
|
|
193
|
+
assert.strictEqual(serialized.aggregateErrors.length, 2)
|
|
194
|
+
assert.strictEqual(serialized.aggregateErrors[0].message, 'foo')
|
|
195
|
+
assert.strictEqual(serialized.aggregateErrors[1].message, 'bar')
|
|
196
|
+
assert.match(serialized.aggregateErrors[0].stack, /^Error: foo/)
|
|
197
|
+
assert.match(serialized.aggregateErrors[1].stack, /^Error: bar/)
|
|
198
|
+
assert.match(serialized.stack, /err\.test\.js:/)
|
|
199
|
+
}
|
|
200
|
+
})
|