@devp0nt/error0 1.0.0-next.4 → 1.0.0-next.41
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/dist/cjs/index.cjs +342 -0
- package/dist/cjs/index.cjs.map +1 -0
- package/dist/cjs/index.d.cts +173 -415
- package/dist/esm/index.d.ts +173 -415
- package/dist/esm/index.js +268 -351
- package/dist/esm/index.js.map +1 -1
- package/package.json +45 -22
- package/src/index.test.ts +295 -478
- package/src/index.ts +566 -508
- package/dist/cjs/index.js +0 -435
- package/dist/cjs/index.js.map +0 -1
package/src/index.test.ts
CHANGED
|
@@ -1,16 +1,15 @@
|
|
|
1
|
-
import { describe, expect, it } from 'bun:test'
|
|
2
|
-
import
|
|
1
|
+
import { describe, expect, expectTypeOf, it } from 'bun:test'
|
|
2
|
+
import type { ClassError0 } from './index.js'
|
|
3
|
+
import { Error0 } from './index.js'
|
|
3
4
|
import z, { ZodError } from 'zod'
|
|
4
|
-
import
|
|
5
|
-
|
|
6
|
-
// TODO: test expected
|
|
5
|
+
import * as assert from 'node:assert'
|
|
7
6
|
|
|
8
7
|
const fixStack = (stack: string | undefined) => {
|
|
9
8
|
if (!stack) {
|
|
10
9
|
return stack
|
|
11
10
|
}
|
|
12
|
-
// at <anonymous> (/Users/
|
|
13
|
-
//
|
|
11
|
+
// at <anonymous> (/Users/x/error0.test.ts:103:25)
|
|
12
|
+
// ↓
|
|
14
13
|
// at <anonymous> (...)
|
|
15
14
|
const lines = stack.split('\n')
|
|
16
15
|
const fixedLines = lines.map((line) => {
|
|
@@ -20,522 +19,340 @@ const fixStack = (stack: string | undefined) => {
|
|
|
20
19
|
return fixedLines.join('\n')
|
|
21
20
|
}
|
|
22
21
|
|
|
23
|
-
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
}
|
|
22
|
+
describe('Error0', () => {
|
|
23
|
+
const statusExtension = Error0.extension()
|
|
24
|
+
.prop('status', {
|
|
25
|
+
init: (input: number) => input,
|
|
26
|
+
resolve: ({ flow }) => flow.find(Boolean),
|
|
27
|
+
serialize: ({ value }) => value,
|
|
28
|
+
deserialize: ({ value }) => (typeof value === 'number' ? value : undefined),
|
|
29
|
+
})
|
|
30
|
+
.method('isStatus', (error, status: number) => error.status === status)
|
|
31
|
+
|
|
32
|
+
const codes = ['NOT_FOUND', 'BAD_REQUEST', 'UNAUTHORIZED'] as const
|
|
33
|
+
type Code = (typeof codes)[number]
|
|
34
|
+
const codeExtension = Error0.extension().extend('prop', 'code', {
|
|
35
|
+
init: (input: Code) => input,
|
|
36
|
+
resolve: ({ flow }) => flow.find(Boolean),
|
|
37
|
+
serialize: ({ value, isPublic }) => (isPublic ? undefined : value),
|
|
38
|
+
deserialize: ({ value }) =>
|
|
39
|
+
typeof value === 'string' && codes.includes(value as Code) ? (value as Code) : undefined,
|
|
40
|
+
})
|
|
28
41
|
|
|
29
|
-
describe('error0', () => {
|
|
30
42
|
it('simple', () => {
|
|
31
|
-
const
|
|
32
|
-
expect(
|
|
33
|
-
expect(
|
|
34
|
-
expect(
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
"cause": undefined,
|
|
39
|
-
"clientMessage": undefined,
|
|
40
|
-
"code": undefined,
|
|
41
|
-
"expected": false,
|
|
42
|
-
"httpStatus": undefined,
|
|
43
|
-
"message": "test",
|
|
44
|
-
"meta": {},
|
|
45
|
-
"stack":
|
|
43
|
+
const error = new Error0('test')
|
|
44
|
+
expect(error).toBeInstanceOf(Error0)
|
|
45
|
+
expect(error).toBeInstanceOf(Error)
|
|
46
|
+
expect(error).toMatchInlineSnapshot(`[Error0: test]`)
|
|
47
|
+
expect(error.message).toBe('test')
|
|
48
|
+
expect(error.stack).toBeDefined()
|
|
49
|
+
expect(fixStack(error.stack)).toMatchInlineSnapshot(`
|
|
46
50
|
"Error0: test
|
|
47
51
|
at <anonymous> (...)"
|
|
48
|
-
,
|
|
49
|
-
"tag": undefined,
|
|
50
|
-
}
|
|
51
|
-
`)
|
|
52
|
-
})
|
|
53
|
-
|
|
54
|
-
it('full', () => {
|
|
55
|
-
const input = {
|
|
56
|
-
message: 'my message',
|
|
57
|
-
tag: 'tag1',
|
|
58
|
-
code: 'code1',
|
|
59
|
-
httpStatus: 400,
|
|
60
|
-
expected: true,
|
|
61
|
-
clientMessage: 'human message 1',
|
|
62
|
-
cause: new Error('original message'),
|
|
63
|
-
meta: {
|
|
64
|
-
reqDurationMs: 1,
|
|
65
|
-
userId: 'user1',
|
|
66
|
-
},
|
|
67
|
-
}
|
|
68
|
-
const error1 = new Error0(input)
|
|
69
|
-
const error2 = new Error0(input.message, input)
|
|
70
|
-
expect(toJSON(error1)).toMatchObject(toJSON(error2))
|
|
71
|
-
expect(toJSON(error1)).toMatchInlineSnapshot(`
|
|
72
|
-
{
|
|
73
|
-
"__I_AM_ERROR_0": true,
|
|
74
|
-
"anyMessage": undefined,
|
|
75
|
-
"cause": [Error: original message],
|
|
76
|
-
"clientMessage": "human message 1",
|
|
77
|
-
"code": "code1",
|
|
78
|
-
"expected": true,
|
|
79
|
-
"httpStatus": 400,
|
|
80
|
-
"message": "my message",
|
|
81
|
-
"meta": {
|
|
82
|
-
"reqDurationMs": 1,
|
|
83
|
-
"userId": "user1",
|
|
84
|
-
},
|
|
85
|
-
"stack":
|
|
86
|
-
"Error0: my message
|
|
87
|
-
at <anonymous> (...)
|
|
88
|
-
|
|
89
|
-
Error: original message
|
|
90
|
-
at <anonymous> (...)"
|
|
91
|
-
,
|
|
92
|
-
"tag": "tag1",
|
|
93
|
-
}
|
|
94
52
|
`)
|
|
95
53
|
})
|
|
96
54
|
|
|
97
|
-
it('
|
|
98
|
-
const
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
"stack":
|
|
114
|
-
"Error0: my message
|
|
115
|
-
at <anonymous> (...)
|
|
116
|
-
|
|
117
|
-
Error: original message
|
|
118
|
-
at <anonymous> (...)"
|
|
119
|
-
,
|
|
120
|
-
"tag": undefined,
|
|
121
|
-
}
|
|
122
|
-
`)
|
|
123
|
-
})
|
|
124
|
-
|
|
125
|
-
it('cause strange thing', () => {
|
|
126
|
-
const error0 = new Error0('my message', { cause: 'strange thing' })
|
|
127
|
-
expect(error0).toMatchInlineSnapshot(`[Error0: my message]`)
|
|
128
|
-
expect(toJSON(error0)).toMatchInlineSnapshot(`
|
|
129
|
-
{
|
|
130
|
-
"__I_AM_ERROR_0": true,
|
|
131
|
-
"anyMessage": undefined,
|
|
132
|
-
"cause": "strange thing",
|
|
133
|
-
"clientMessage": undefined,
|
|
134
|
-
"code": undefined,
|
|
135
|
-
"expected": false,
|
|
136
|
-
"httpStatus": undefined,
|
|
137
|
-
"message": "my message",
|
|
138
|
-
"meta": {},
|
|
139
|
-
"stack":
|
|
140
|
-
"Error0: my message
|
|
55
|
+
it('with direct prop extension', () => {
|
|
56
|
+
const AppError = Error0.extend('prop', 'status', {
|
|
57
|
+
init: (input: number) => input,
|
|
58
|
+
resolve: ({ flow }) => flow.find(Boolean),
|
|
59
|
+
serialize: ({ value }) => value,
|
|
60
|
+
deserialize: ({ value }) => (typeof value === 'number' ? value : undefined),
|
|
61
|
+
})
|
|
62
|
+
const error = new AppError('test', { status: 400 })
|
|
63
|
+
expect(error).toBeInstanceOf(AppError)
|
|
64
|
+
expect(error).toBeInstanceOf(Error0)
|
|
65
|
+
expect(error).toBeInstanceOf(Error)
|
|
66
|
+
expect(error.status).toBe(400)
|
|
67
|
+
expect(error).toMatchInlineSnapshot(`[Error0: test]`)
|
|
68
|
+
expect(error.stack).toBeDefined()
|
|
69
|
+
expect(fixStack(error.stack)).toMatchInlineSnapshot(`
|
|
70
|
+
"Error0: test
|
|
141
71
|
at <anonymous> (...)"
|
|
142
|
-
,
|
|
143
|
-
"tag": undefined,
|
|
144
|
-
}
|
|
145
72
|
`)
|
|
73
|
+
expectTypeOf<typeof AppError>().toExtend<ClassError0>()
|
|
146
74
|
})
|
|
147
75
|
|
|
148
|
-
it('
|
|
149
|
-
const
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
reqDurationMs: 1,
|
|
154
|
-
userId: 'user1',
|
|
76
|
+
it('class helpers prop/method/refine mirror extend API', () => {
|
|
77
|
+
const AppError = Error0.prop('status', {
|
|
78
|
+
init: (value: number) => value,
|
|
79
|
+
resolve: ({ value, flow }) => {
|
|
80
|
+
return typeof value === 'number' ? value : undefined
|
|
155
81
|
},
|
|
82
|
+
serialize: ({ value }) => value,
|
|
83
|
+
deserialize: ({ value }) => (typeof value === 'number' ? value : undefined),
|
|
156
84
|
})
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
expect(toJSON(error02)).toMatchInlineSnapshot(`
|
|
171
|
-
{
|
|
172
|
-
"__I_AM_ERROR_0": true,
|
|
173
|
-
"anyMessage": undefined,
|
|
174
|
-
"cause": [Error0: first],
|
|
175
|
-
"clientMessage": "human message 1",
|
|
176
|
-
"code": "code2",
|
|
177
|
-
"expected": false,
|
|
178
|
-
"httpStatus": undefined,
|
|
179
|
-
"message": "second",
|
|
180
|
-
"meta": {
|
|
181
|
-
"ideaId": "idea1",
|
|
182
|
-
"other": {
|
|
183
|
-
"x": 1,
|
|
184
|
-
},
|
|
185
|
-
"reqDurationMs": 1,
|
|
186
|
-
"userId": "user1",
|
|
187
|
-
},
|
|
188
|
-
"stack":
|
|
189
|
-
"Error0: second
|
|
190
|
-
at <anonymous> (...)
|
|
191
|
-
|
|
192
|
-
Error0: first
|
|
193
|
-
at <anonymous> (...)"
|
|
194
|
-
,
|
|
195
|
-
"tag": "tag2",
|
|
196
|
-
}
|
|
197
|
-
`)
|
|
85
|
+
.method('isStatus', (error, expectedStatus: number) => error.status === expectedStatus)
|
|
86
|
+
.refine((error) => {
|
|
87
|
+
if (error.cause instanceof Error && error.status === undefined) {
|
|
88
|
+
return { status: 500 }
|
|
89
|
+
}
|
|
90
|
+
return undefined
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
const error = AppError.from(new Error('inner'))
|
|
94
|
+
expect(error.status).toBe(500)
|
|
95
|
+
expect(error.isStatus(500)).toBe(true)
|
|
96
|
+
expect(AppError.isStatus(error, 500)).toBe(true)
|
|
97
|
+
expectTypeOf<typeof AppError>().toExtend<ClassError0>()
|
|
198
98
|
})
|
|
199
99
|
|
|
200
|
-
it('
|
|
201
|
-
const
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
"message": "Unknown error",
|
|
212
|
-
"meta": {},
|
|
213
|
-
"stack":
|
|
214
|
-
"Error0: Unknown error
|
|
215
|
-
at <anonymous> (...)"
|
|
216
|
-
,
|
|
217
|
-
"tag": undefined,
|
|
218
|
-
}
|
|
219
|
-
`)
|
|
220
|
-
const error1 = new Error0('test')
|
|
221
|
-
expect(error1.message).toBe('test')
|
|
222
|
-
const error2 = new Error0({ cause: error1 })
|
|
223
|
-
expect(toJSON(error2)).toMatchInlineSnapshot(`
|
|
224
|
-
{
|
|
225
|
-
"__I_AM_ERROR_0": true,
|
|
226
|
-
"anyMessage": undefined,
|
|
227
|
-
"cause": [Error0: test],
|
|
228
|
-
"clientMessage": undefined,
|
|
229
|
-
"code": undefined,
|
|
230
|
-
"expected": false,
|
|
231
|
-
"httpStatus": undefined,
|
|
232
|
-
"message": "Unknown error",
|
|
233
|
-
"meta": {},
|
|
234
|
-
"stack":
|
|
235
|
-
"Error0: Unknown error
|
|
236
|
-
at <anonymous> (...)
|
|
237
|
-
|
|
238
|
-
Error0: test
|
|
239
|
-
at <anonymous> (...)"
|
|
240
|
-
,
|
|
241
|
-
"tag": undefined,
|
|
242
|
-
}
|
|
243
|
-
`)
|
|
244
|
-
expect(fixStack(error2.stack)).toMatchInlineSnapshot(`
|
|
245
|
-
"Error0: Unknown error
|
|
246
|
-
at <anonymous> (...)
|
|
247
|
-
|
|
248
|
-
Error0: test
|
|
100
|
+
it('with defined extension', () => {
|
|
101
|
+
const AppError = Error0.extend(statusExtension)
|
|
102
|
+
const error = new AppError('test', { status: 400 })
|
|
103
|
+
expect(error).toBeInstanceOf(AppError)
|
|
104
|
+
expect(error).toBeInstanceOf(Error0)
|
|
105
|
+
expect(error).toBeInstanceOf(Error)
|
|
106
|
+
expect(error.status).toBe(400)
|
|
107
|
+
expect(error).toMatchInlineSnapshot(`[Error0: test]`)
|
|
108
|
+
expect(error.stack).toBeDefined()
|
|
109
|
+
expect(fixStack(error.stack)).toMatchInlineSnapshot(`
|
|
110
|
+
"Error0: test
|
|
249
111
|
at <anonymous> (...)"
|
|
250
112
|
`)
|
|
251
113
|
})
|
|
252
114
|
|
|
253
|
-
it('
|
|
254
|
-
const
|
|
255
|
-
const
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
"stack":
|
|
268
|
-
"Error0: Unknown error
|
|
269
|
-
at <anonymous> (...)
|
|
270
|
-
|
|
271
|
-
Error: default error
|
|
272
|
-
at <anonymous> (...)"
|
|
273
|
-
,
|
|
274
|
-
"tag": undefined,
|
|
275
|
-
}
|
|
276
|
-
`)
|
|
277
|
-
expect(fixStack(error0.stack)).toMatchInlineSnapshot(`
|
|
278
|
-
"Error0: Unknown error
|
|
279
|
-
at <anonymous> (...)
|
|
280
|
-
|
|
281
|
-
Error: default error
|
|
282
|
-
at <anonymous> (...)"
|
|
283
|
-
`)
|
|
115
|
+
it('twice extended Error0 extends previous by types', () => {
|
|
116
|
+
const AppError1 = Error0.extend(statusExtension)
|
|
117
|
+
const AppError2 = AppError1.extend(codeExtension)
|
|
118
|
+
const error1 = new AppError1('test', { status: 400 })
|
|
119
|
+
const error2 = new AppError2('test', { status: 400, code: 'NOT_FOUND' })
|
|
120
|
+
expect(error1.status).toBe(400)
|
|
121
|
+
expect(error2.status).toBe(400)
|
|
122
|
+
expect(error2.code).toBe('NOT_FOUND')
|
|
123
|
+
expectTypeOf<typeof error2.status>().toEqualTypeOf<number | undefined>()
|
|
124
|
+
expectTypeOf<typeof error2.code>().toEqualTypeOf<'NOT_FOUND' | 'BAD_REQUEST' | 'UNAUTHORIZED' | undefined>()
|
|
125
|
+
expectTypeOf<typeof AppError1>().toExtend<ClassError0>()
|
|
126
|
+
expectTypeOf<typeof AppError2>().toExtend<ClassError0>()
|
|
127
|
+
expectTypeOf<typeof AppError2>().toExtend<typeof AppError1>()
|
|
128
|
+
expectTypeOf<typeof AppError1>().not.toExtend<typeof AppError2>()
|
|
284
129
|
})
|
|
285
130
|
|
|
286
|
-
it('
|
|
287
|
-
const
|
|
288
|
-
const
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
"code": undefined,
|
|
296
|
-
"expected": false,
|
|
297
|
-
"httpStatus": undefined,
|
|
298
|
-
"message": "Unknown error",
|
|
299
|
-
"meta": {},
|
|
300
|
-
"stack":
|
|
301
|
-
"Error0: Unknown error
|
|
302
|
-
at <anonymous> (...)
|
|
303
|
-
|
|
304
|
-
Error0: error0 error
|
|
305
|
-
at <anonymous> (...)"
|
|
306
|
-
,
|
|
307
|
-
"tag": undefined,
|
|
308
|
-
}
|
|
309
|
-
`)
|
|
310
|
-
expect(fixStack(error0.stack)).toMatchInlineSnapshot(`
|
|
311
|
-
"Error0: Unknown error
|
|
312
|
-
at <anonymous> (...)
|
|
313
|
-
|
|
314
|
-
Error0: error0 error
|
|
131
|
+
it('can have cause', () => {
|
|
132
|
+
const AppError = Error0.extend(statusExtension)
|
|
133
|
+
const anotherError = new Error('another error')
|
|
134
|
+
const error = new AppError('test', { status: 400, cause: anotherError })
|
|
135
|
+
expect(error.status).toBe(400)
|
|
136
|
+
expect(error).toMatchInlineSnapshot(`[Error0: test]`)
|
|
137
|
+
expect(error.stack).toBeDefined()
|
|
138
|
+
expect(fixStack(error.stack)).toMatchInlineSnapshot(`
|
|
139
|
+
"Error0: test
|
|
315
140
|
at <anonymous> (...)"
|
|
316
141
|
`)
|
|
142
|
+
expect(Error0.causes(error)).toEqual([error, anotherError])
|
|
317
143
|
})
|
|
318
144
|
|
|
319
|
-
it('
|
|
320
|
-
const
|
|
321
|
-
const
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
tag: 'tag2',
|
|
328
|
-
code: 'code2',
|
|
329
|
-
cause: error01,
|
|
330
|
-
})
|
|
331
|
-
expect(fixStack(errorDefault.stack)).toMatchInlineSnapshot(`
|
|
332
|
-
"Error: default error
|
|
333
|
-
at <anonymous> (...)"
|
|
334
|
-
`)
|
|
335
|
-
expect(fixStack(error01.stack)).toMatchInlineSnapshot(`
|
|
336
|
-
"Error0: first
|
|
337
|
-
at <anonymous> (...)
|
|
338
|
-
|
|
339
|
-
Error: default error
|
|
340
|
-
at <anonymous> (...)"
|
|
341
|
-
`)
|
|
342
|
-
expect(fixStack(error02.stack)).toMatchInlineSnapshot(`
|
|
343
|
-
"Error0: second
|
|
344
|
-
at <anonymous> (...)
|
|
345
|
-
|
|
346
|
-
Error0: first
|
|
347
|
-
at <anonymous> (...)
|
|
348
|
-
|
|
349
|
-
Error: default error
|
|
350
|
-
at <anonymous> (...)"
|
|
351
|
-
`)
|
|
145
|
+
it('can have many causes', () => {
|
|
146
|
+
const AppError = Error0.extend(statusExtension)
|
|
147
|
+
const anotherError = new Error('another error')
|
|
148
|
+
const error1 = new AppError('test1', { status: 400, cause: anotherError })
|
|
149
|
+
const error2 = new AppError('test2', { status: 400, cause: error1 })
|
|
150
|
+
expect(error1.status).toBe(400)
|
|
151
|
+
expect(error2.status).toBe(400)
|
|
152
|
+
expect(Error0.causes(error2)).toEqual([error2, error1, anotherError])
|
|
352
153
|
})
|
|
353
154
|
|
|
354
|
-
it('
|
|
355
|
-
const
|
|
356
|
-
|
|
357
|
-
})
|
|
358
|
-
|
|
155
|
+
it('properties floating', () => {
|
|
156
|
+
const AppError = Error0.extend(statusExtension).extend(codeExtension)
|
|
157
|
+
const anotherError = new Error('another error')
|
|
158
|
+
const error1 = new AppError('test1', { status: 400, cause: anotherError })
|
|
159
|
+
const error2 = new AppError('test2', { code: 'NOT_FOUND', cause: error1 })
|
|
160
|
+
expect(error1.status).toBe(400)
|
|
161
|
+
expect(error1.code).toBe(undefined)
|
|
162
|
+
expect(error2.status).toBe(400)
|
|
163
|
+
expect(error2.code).toBe('NOT_FOUND')
|
|
164
|
+
expect(Error0.causes(error2)).toEqual([error2, error1, anotherError])
|
|
165
|
+
})
|
|
359
166
|
|
|
360
|
-
|
|
361
|
-
|
|
167
|
+
it('serialize uses identity by default and skips undefined extension values', () => {
|
|
168
|
+
const AppError = Error0.extend(statusExtension).prop('code', {
|
|
169
|
+
init: (input: string) => input,
|
|
170
|
+
resolve: ({ flow }) => flow.find(Boolean),
|
|
171
|
+
serialize: () => undefined,
|
|
172
|
+
deserialize: ({ value }) => (typeof value === 'string' ? value : undefined),
|
|
362
173
|
})
|
|
363
|
-
|
|
174
|
+
const error = new AppError('test', { status: 401, code: 'secret' })
|
|
175
|
+
const json = AppError.serialize(error)
|
|
176
|
+
expect(json.status).toBe(401)
|
|
177
|
+
expect('code' in json).toBe(false)
|
|
178
|
+
})
|
|
364
179
|
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
expect(
|
|
180
|
+
it('serialize keeps stack by default without stack extension', () => {
|
|
181
|
+
const AppError = Error0.extend(statusExtension)
|
|
182
|
+
const error = new AppError('test', { status: 500 })
|
|
183
|
+
const json = AppError.serialize(error)
|
|
184
|
+
expect(json.stack).toBe(error.stack)
|
|
185
|
+
})
|
|
370
186
|
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
187
|
+
it('stack extension can customize serialization of stack prop', () => {
|
|
188
|
+
const AppError = Error0.prop('stack', {
|
|
189
|
+
init: (input: string) => input,
|
|
190
|
+
resolve: ({ value }) => (typeof value === 'string' ? value : undefined),
|
|
191
|
+
serialize: ({ value }) => undefined,
|
|
192
|
+
deserialize: ({ value }) => (typeof value === 'string' ? value : undefined),
|
|
374
193
|
})
|
|
375
|
-
|
|
194
|
+
const error = new AppError('test')
|
|
195
|
+
const json = AppError.serialize(error)
|
|
196
|
+
expect('stack' in json).toBe(false)
|
|
197
|
+
})
|
|
376
198
|
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
199
|
+
it('.serialize() -> .from() roundtrip keeps extension values', () => {
|
|
200
|
+
const AppError = Error0.extend(statusExtension).extend(codeExtension)
|
|
201
|
+
const error = new AppError('test', { status: 409, code: 'NOT_FOUND' })
|
|
202
|
+
const json = AppError.serialize(error, false)
|
|
203
|
+
const recreated = AppError.from(json)
|
|
204
|
+
expect(recreated).toBeInstanceOf(AppError)
|
|
205
|
+
expect(recreated.status).toBe(409)
|
|
206
|
+
expect(recreated.code).toBe('NOT_FOUND')
|
|
207
|
+
expect(AppError.serialize(recreated, false)).toEqual(json)
|
|
208
|
+
})
|
|
382
209
|
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
})
|
|
387
|
-
|
|
210
|
+
it('.serialize() floated props and not serialize causes', () => {
|
|
211
|
+
const AppError = Error0.extend(statusExtension).extend(codeExtension)
|
|
212
|
+
const error1 = new AppError('test', { status: 409 })
|
|
213
|
+
const error2 = new AppError('test', { code: 'NOT_FOUND', cause: error1 })
|
|
214
|
+
const json = AppError.serialize(error2, false)
|
|
215
|
+
expect(json.status).toBe(409)
|
|
216
|
+
expect(json.code).toBe('NOT_FOUND')
|
|
217
|
+
expect('cause' in json).toBe(false)
|
|
388
218
|
})
|
|
389
219
|
|
|
390
|
-
it('
|
|
391
|
-
const
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
expect(
|
|
396
|
-
expect(
|
|
397
|
-
{
|
|
398
|
-
"__I_AM_ERROR_0": true,
|
|
399
|
-
"anyMessage": undefined,
|
|
400
|
-
"cause": undefined,
|
|
401
|
-
"clientMessage": undefined,
|
|
402
|
-
"code": undefined,
|
|
403
|
-
"expected": true,
|
|
404
|
-
"httpStatus": undefined,
|
|
405
|
-
"message": "expected error",
|
|
406
|
-
"meta": {},
|
|
407
|
-
"stack":
|
|
408
|
-
"Error0: expected error
|
|
409
|
-
at <anonymous> (...)"
|
|
410
|
-
,
|
|
411
|
-
"tag": undefined,
|
|
412
|
-
}
|
|
413
|
-
`)
|
|
220
|
+
it('serialize can hide props for public output', () => {
|
|
221
|
+
const AppError = Error0.extend(statusExtension).extend(codeExtension)
|
|
222
|
+
const error = new AppError('test', { status: 401, code: 'NOT_FOUND' })
|
|
223
|
+
const privateJson = AppError.serialize(error, false)
|
|
224
|
+
const publicJson = AppError.serialize(error, true)
|
|
225
|
+
expect(privateJson.code).toBe('NOT_FOUND')
|
|
226
|
+
expect('code' in publicJson).toBe(false)
|
|
414
227
|
})
|
|
415
228
|
|
|
416
|
-
it('
|
|
417
|
-
const
|
|
418
|
-
|
|
419
|
-
defaultMeta: {
|
|
420
|
-
tagPrefix: 'nested',
|
|
421
|
-
},
|
|
229
|
+
it('by default error0 created from another error has same message', () => {
|
|
230
|
+
const schema = z.object({
|
|
231
|
+
x: z.string(),
|
|
422
232
|
})
|
|
423
|
-
const
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
expect(
|
|
428
|
-
{
|
|
429
|
-
"__I_AM_ERROR_0": true,
|
|
430
|
-
"anyMessage": undefined,
|
|
431
|
-
"cause": undefined,
|
|
432
|
-
"clientMessage": undefined,
|
|
433
|
-
"code": undefined,
|
|
434
|
-
"expected": false,
|
|
435
|
-
"httpStatus": undefined,
|
|
436
|
-
"message": "nested error",
|
|
437
|
-
"meta": {
|
|
438
|
-
"tagPrefix": "nested",
|
|
439
|
-
},
|
|
440
|
-
"stack":
|
|
441
|
-
"Error0: nested error
|
|
442
|
-
at <anonymous> (...)"
|
|
443
|
-
,
|
|
444
|
-
"tag": "nested:nested",
|
|
445
|
-
}
|
|
446
|
-
`)
|
|
447
|
-
|
|
448
|
-
const error02 = new e0s1.Expected('nested error 1')
|
|
449
|
-
expect(error02).toBeInstanceOf(e0s1.Expected)
|
|
450
|
-
expect(error02).toBeInstanceOf(e0s.Expected)
|
|
451
|
-
expect(error02).toBeInstanceOf(Error0)
|
|
452
|
-
expect(error02.expected).toBe(true)
|
|
453
|
-
expect(toJSON(error02)).toMatchInlineSnapshot(`
|
|
454
|
-
{
|
|
455
|
-
"__I_AM_ERROR_0": true,
|
|
456
|
-
"anyMessage": undefined,
|
|
457
|
-
"cause": undefined,
|
|
458
|
-
"clientMessage": undefined,
|
|
459
|
-
"code": undefined,
|
|
460
|
-
"expected": true,
|
|
461
|
-
"httpStatus": undefined,
|
|
462
|
-
"message": "nested error 1",
|
|
463
|
-
"meta": {
|
|
464
|
-
"tagPrefix": "nested",
|
|
465
|
-
},
|
|
466
|
-
"stack":
|
|
467
|
-
"Error0: nested error 1
|
|
468
|
-
at <anonymous> (...)"
|
|
469
|
-
,
|
|
470
|
-
"tag": "nested:nested",
|
|
471
|
-
}
|
|
472
|
-
`)
|
|
233
|
+
const parseResult = schema.safeParse({ x: 123 })
|
|
234
|
+
const parsedError = parseResult.error
|
|
235
|
+
assert.ok(parsedError)
|
|
236
|
+
const error = Error0.from(parsedError)
|
|
237
|
+
expect(error.message).toBe(parsedError.message)
|
|
473
238
|
})
|
|
474
239
|
|
|
475
|
-
it('
|
|
476
|
-
const
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
240
|
+
it('refine message and other props via direct transformations', () => {
|
|
241
|
+
const schema = z.object({
|
|
242
|
+
x: z.string(),
|
|
243
|
+
})
|
|
244
|
+
const parseResult = schema.safeParse({ x: 123 })
|
|
245
|
+
const parsedError = parseResult.error
|
|
246
|
+
assert.ok(parsedError)
|
|
247
|
+
const AppError = Error0.extend(statusExtension)
|
|
248
|
+
.extend(codeExtension)
|
|
249
|
+
.extend('refine', (error) => {
|
|
250
|
+
if (error.cause instanceof ZodError) {
|
|
251
|
+
error.status = 422
|
|
252
|
+
error.code = 'NOT_FOUND'
|
|
253
|
+
error.message = `Validation Error: ${error.message}`
|
|
254
|
+
}
|
|
255
|
+
})
|
|
256
|
+
const error = AppError.from(parsedError)
|
|
257
|
+
expect(error.message).toBe(`Validation Error: ${parsedError.message}`)
|
|
258
|
+
expect(error.status).toBe(422)
|
|
259
|
+
expect(error.code).toBe('NOT_FOUND')
|
|
260
|
+
const error1 = new AppError('test', { cause: parsedError })
|
|
261
|
+
expect(error1.message).toBe('test')
|
|
262
|
+
expect(error1.status).toBe(undefined)
|
|
263
|
+
expect(error1.code).toBe(undefined)
|
|
484
264
|
})
|
|
485
265
|
|
|
486
|
-
it('from
|
|
487
|
-
const
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
{
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
266
|
+
it('refine message and other props via return output values from extension', () => {
|
|
267
|
+
const schema = z.object({
|
|
268
|
+
x: z.string(),
|
|
269
|
+
})
|
|
270
|
+
const parseResult = schema.safeParse({ x: 123 })
|
|
271
|
+
const parsedError = parseResult.error
|
|
272
|
+
assert.ok(parsedError)
|
|
273
|
+
const AppError = Error0.extend(statusExtension)
|
|
274
|
+
.extend(codeExtension)
|
|
275
|
+
.extend('refine', (error) => {
|
|
276
|
+
if (error.cause instanceof ZodError) {
|
|
277
|
+
error.message = `Validation Error: ${error.message}`
|
|
278
|
+
return {
|
|
279
|
+
status: 422,
|
|
280
|
+
code: 'NOT_FOUND',
|
|
281
|
+
}
|
|
501
282
|
}
|
|
502
|
-
|
|
503
|
-
|
|
283
|
+
return undefined
|
|
284
|
+
})
|
|
285
|
+
const error = AppError.from(parsedError)
|
|
286
|
+
expect(error.message).toBe(`Validation Error: ${parsedError.message}`)
|
|
287
|
+
expect(error.status).toBe(422)
|
|
288
|
+
expect(error.code).toBe('NOT_FOUND')
|
|
289
|
+
const error1 = new AppError('test', { cause: parsedError })
|
|
290
|
+
expect(error1.message).toBe('test')
|
|
291
|
+
expect(error1.status).toBe(undefined)
|
|
292
|
+
expect(error1.code).toBe(undefined)
|
|
504
293
|
})
|
|
505
294
|
|
|
506
|
-
it('
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
expect(
|
|
531
|
-
|
|
532
|
-
expect(error0.axiosError).toBe(axiosError)
|
|
533
|
-
expect(error0.message).toBe('Axios Error')
|
|
534
|
-
expect(error0.meta).toMatchInlineSnapshot(`
|
|
535
|
-
{
|
|
536
|
-
"axiosData": "{"error":"Invalid input","details":["Field X is required"]}",
|
|
537
|
-
"axiosStatus": 400,
|
|
538
|
-
}
|
|
539
|
-
`)
|
|
295
|
+
it('expected prop can be realized to send or not to send error to your error tracker', () => {
|
|
296
|
+
const AppError = Error0.extend(statusExtension)
|
|
297
|
+
.prop('expected', {
|
|
298
|
+
init: (input: boolean) => input,
|
|
299
|
+
resolve: ({ flow }) => flow.find((value) => typeof value === 'boolean'),
|
|
300
|
+
serialize: ({ value }) => value,
|
|
301
|
+
deserialize: ({ value }) => (typeof value === 'boolean' ? value : undefined),
|
|
302
|
+
})
|
|
303
|
+
.method('isExpected', (error) => {
|
|
304
|
+
return error.expected ?? false
|
|
305
|
+
})
|
|
306
|
+
const errorExpected = new AppError('test', { status: 400, expected: true })
|
|
307
|
+
const errorUnexpected = new AppError('test', { status: 400, expected: false })
|
|
308
|
+
const usualError = new Error('test')
|
|
309
|
+
const errorFromUsualError = AppError.from(usualError)
|
|
310
|
+
const errorWithExpectedErrorAsCause = new AppError('test', { status: 400, cause: errorExpected })
|
|
311
|
+
const errorWithUnexpectedErrorAsCause = new AppError('test', { status: 400, cause: errorUnexpected })
|
|
312
|
+
expect(errorExpected.expected).toBe(true)
|
|
313
|
+
expect(errorUnexpected.expected).toBe(false)
|
|
314
|
+
expect(AppError.isExpected(usualError)).toBe(false)
|
|
315
|
+
expect(errorFromUsualError.expected).toBe(undefined)
|
|
316
|
+
expect(errorFromUsualError.isExpected()).toBe(false)
|
|
317
|
+
expect(errorWithExpectedErrorAsCause.expected).toBe(true)
|
|
318
|
+
expect(errorWithExpectedErrorAsCause.isExpected()).toBe(true)
|
|
319
|
+
expect(errorWithUnexpectedErrorAsCause.expected).toBe(false)
|
|
320
|
+
expect(errorWithUnexpectedErrorAsCause.isExpected()).toBe(false)
|
|
540
321
|
})
|
|
322
|
+
|
|
323
|
+
// we will have no variants
|
|
324
|
+
// becouse you can thorw any errorm and when you do AppError.from(yourError)
|
|
325
|
+
// can use refine to assign desired props to error, it is enough for transport
|
|
326
|
+
// you even can create computed or method to retrieve your error, so no problems with variants
|
|
327
|
+
|
|
328
|
+
// it('can create and recongnize variant', () => {
|
|
329
|
+
// const AppError = Error0.extend(statusExtension)
|
|
330
|
+
// .extend(codeExtension)
|
|
331
|
+
// .extend('prop', 'userId', {
|
|
332
|
+
// input: (value: string) => value,
|
|
333
|
+
// output: (error) => {
|
|
334
|
+
// for (const value of error.flow('userId')) {
|
|
335
|
+
// if (typeof value === 'string') {
|
|
336
|
+
// return value
|
|
337
|
+
// }
|
|
338
|
+
// }
|
|
339
|
+
// return undefined
|
|
340
|
+
// },
|
|
341
|
+
// serialize: (value) => value,
|
|
342
|
+
// })
|
|
343
|
+
// const UserError = AppError.variant('UserError', {
|
|
344
|
+
// userId: true,
|
|
345
|
+
// })
|
|
346
|
+
// const error = new UserError('test', { userId: '123', status: 400 })
|
|
347
|
+
// expect(error).toBeInstanceOf(UserError)
|
|
348
|
+
// expect(error).toBeInstanceOf(AppError)
|
|
349
|
+
// expect(error).toBeInstanceOf(Error0)
|
|
350
|
+
// expect(error).toBeInstanceOf(Error)
|
|
351
|
+
// expect(error.userId).toBe('123')
|
|
352
|
+
// expect(error.status).toBe(400)
|
|
353
|
+
// expect(error.code).toBe(undefined)
|
|
354
|
+
// expectTypeOf<typeof error.userId>().toEqualTypeOf<string>()
|
|
355
|
+
// // @ts-expect-error
|
|
356
|
+
// new UserError('test')
|
|
357
|
+
// })
|
|
541
358
|
})
|