@devp0nt/error0 1.0.0-next.38 → 1.0.0-next.39

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