@devp0nt/error0 1.0.0-next.4 → 1.0.0-next.40

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,16 +1,15 @@
1
- import { describe, expect, it } from 'bun:test'
2
- import { type AxiosError, isAxiosError } from 'axios'
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 { Error0, e0s } from './index.js'
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/iserdmi/cc/projects/svagatron/modules/lib/error0.test.ts:103:25)
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,360 @@ const fixStack = (stack: string | undefined) => {
20
19
  return fixedLines.join('\n')
21
20
  }
22
21
 
23
- const toJSON = (error: Error0) => {
24
- const result = error.toJSON()
25
- result.stack = fixStack(error.stack)
26
- return result
27
- }
28
-
29
- describe('error0', () => {
30
- it('simple', () => {
31
- const error0 = new Error0('test')
32
- expect(error0).toBeInstanceOf(Error0)
33
- expect(error0).toMatchInlineSnapshot(`[Error0: test]`)
34
- expect(toJSON(error0)).toMatchInlineSnapshot(`
35
- {
36
- "__I_AM_ERROR_0": true,
37
- "anyMessage": undefined,
38
- "cause": undefined,
39
- "clientMessage": undefined,
40
- "code": undefined,
41
- "expected": false,
42
- "httpStatus": undefined,
43
- "message": "test",
44
- "meta": {},
45
- "stack":
46
- "Error0: test
47
- 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',
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
66
34
  },
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
- `)
95
- })
96
-
97
- it('cause error default', () => {
98
- const errorDefault = new Error('original message')
99
- const error0 = new Error0('my message', { cause: errorDefault })
100
- expect(error0).toBeInstanceOf(Error0)
101
- expect(error0).toMatchInlineSnapshot(`[Error0: my message]`)
102
- expect(toJSON(error0)).toMatchInlineSnapshot(`
103
- {
104
- "__I_AM_ERROR_0": true,
105
- "anyMessage": undefined,
106
- "cause": [Error: original message],
107
- "clientMessage": undefined,
108
- "code": undefined,
109
- "expected": false,
110
- "httpStatus": undefined,
111
- "message": "my message",
112
- "meta": {},
113
- "stack":
114
- "Error0: my message
115
- at <anonymous> (...)
116
-
117
- Error: original message
118
- at <anonymous> (...)"
119
- ,
120
- "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
+ }
121
50
  }
122
- `)
51
+ return undefined
52
+ },
53
+ serialize: (value, error, isPublic) => (isPublic ? undefined : value),
123
54
  })
124
55
 
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
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
141
65
  at <anonymous> (...)"
142
- ,
143
- "tag": undefined,
144
- }
145
66
  `)
146
67
  })
147
68
 
148
- it('floats and overrides', () => {
149
- const error01 = new Error0('first', {
150
- tag: 'tag1',
151
- clientMessage: 'human message 1',
152
- meta: {
153
- reqDurationMs: 1,
154
- userId: 'user1',
155
- },
156
- })
157
- const error02 = new Error0('second', {
158
- tag: 'tag2',
159
- code: 'code2',
160
- cause: error01,
161
- meta: {
162
- reqDurationMs: 1,
163
- ideaId: 'idea1',
164
- other: {
165
- x: 1,
166
- },
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
167
80
  },
81
+ serialize: (value: number | undefined) => value,
168
82
  })
169
- expect(error01).toBeInstanceOf(Error0)
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
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
193
92
  at <anonymous> (...)"
194
- ,
195
- "tag": "tag2",
196
- }
197
93
  `)
94
+ expectTypeOf<typeof AppError>().toExtend<ClassError0>()
198
95
  })
199
96
 
200
- it('unknown error', () => {
201
- const error0 = new Error0({})
202
- expect(toJSON(error0)).toMatchInlineSnapshot(`
203
- {
204
- "__I_AM_ERROR_0": true,
205
- "anyMessage": undefined,
206
- "cause": undefined,
207
- "clientMessage": undefined,
208
- "code": undefined,
209
- "expected": false,
210
- "httpStatus": undefined,
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
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
249
108
  at <anonymous> (...)"
250
109
  `)
251
110
  })
252
111
 
253
- it('input error default', () => {
254
- const errorDefault = new Error('default error')
255
- const error0 = new Error0(errorDefault)
256
- expect(toJSON(error0)).toMatchInlineSnapshot(`
257
- {
258
- "__I_AM_ERROR_0": true,
259
- "anyMessage": undefined,
260
- "cause": [Error: default error],
261
- "clientMessage": undefined,
262
- "code": undefined,
263
- "expected": false,
264
- "httpStatus": undefined,
265
- "message": "Unknown error",
266
- "meta": {},
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
- `)
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>()
284
126
  })
285
127
 
286
- it('input error0 itself', () => {
287
- const error = new Error0('error0 error')
288
- const error0 = new Error0(error)
289
- expect(toJSON(error0)).toMatchInlineSnapshot(`
290
- {
291
- "__I_AM_ERROR_0": true,
292
- "anyMessage": undefined,
293
- "cause": [Error0: error0 error],
294
- "clientMessage": undefined,
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
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
315
137
  at <anonymous> (...)"
316
138
  `)
139
+ expect(Error0.causes(error)).toEqual([error, anotherError])
317
140
  })
318
141
 
319
- it('keep stack trace', () => {
320
- const errorDefault = new Error('default error')
321
- const error01 = new Error0('first', {
322
- tag: 'tag1',
323
- clientMessage: 'human message 1',
324
- cause: errorDefault,
325
- })
326
- const error02 = new Error0('second', {
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
- `)
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])
352
150
  })
353
151
 
354
- it('expected', () => {
355
- const error0 = new Error0({
356
- expected: true,
357
- })
358
- 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
+ })
359
163
 
360
- const error1 = new Error0({
361
- 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,
362
176
  })
363
- 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
+ })
364
182
 
365
- const error3 = new Error0({
366
- expected: true,
367
- cause: error0,
368
- })
369
- 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
+ })
370
189
 
371
- const error4 = new Error0({
372
- expected: false,
373
- 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,
374
201
  })
375
- 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
+ })
376
206
 
377
- const error5 = new Error0({
378
- expected: true,
379
- cause: error1,
380
- })
381
- 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
+ })
382
217
 
383
- const error6 = new Error0({
384
- expected: false,
385
- cause: error1,
386
- })
387
- 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)
388
232
  })
389
233
 
390
- it('extends self', () => {
391
- const error7 = new e0s.Expected('expected error')
392
- expect(e0s.Expected.defaultExpected).toBe(true)
393
- expect(error7.expected).toBe(true)
394
- expect(error7).toBeInstanceOf(e0s.Expected)
395
- expect(error7).toBeInstanceOf(Error0)
396
- expect(toJSON(error7)).toMatchInlineSnapshot(`
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
- `)
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)
414
241
  })
415
242
 
416
- it('extend collection', () => {
417
- const e0s1 = Error0.extendCollection(e0s, {
418
- defaultMessage: 'nested error',
419
- defaultMeta: {
420
- tagPrefix: 'nested',
421
- },
243
+ it('by default error0 created from another error has same message', () => {
244
+ const schema = z.object({
245
+ x: z.string(),
422
246
  })
423
- const error0 = new e0s1.Default('nested error')
424
- expect(error0).toBeInstanceOf(e0s1.Default)
425
- expect(error0).toBeInstanceOf(e0s.Default)
426
- expect(error0).toBeInstanceOf(Error0)
427
- expect(toJSON(error0)).toMatchInlineSnapshot(`
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
- `)
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)
473
252
  })
474
253
 
475
- it('cause zod error', () => {
476
- const zodError = z.object({ x: z.number() }).safeParse('test').error
477
- if (!zodError) {
478
- throw new Error('zodError is undefined')
479
- }
480
- expect(zodError).toBeInstanceOf(ZodError)
481
- const error0 = new Error0(zodError)
482
- expect(error0.zodError).toBe(zodError)
483
- 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)
484
278
  })
485
279
 
486
- it('from zod error', () => {
487
- const zodError = z.object({ x: z.number() }).safeParse('test').error
488
- if (!zodError) {
489
- throw new Error('zodError is undefined')
490
- }
491
- expect(zodError).toBeInstanceOf(ZodError)
492
- const error0 = Error0.from(zodError)
493
- expect(error0.zodError).toBe(zodError)
494
- expect(error0.message).toMatchInlineSnapshot(`
495
- "Zod Validation Error: [
496
- {
497
- "expected": "object",
498
- "code": "invalid_type",
499
- "path": [],
500
- "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
+ }
501
296
  }
502
- ]"
503
- `)
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)
504
307
  })
505
308
 
506
- it('from axios error', async () => {
507
- function makeFakeAxiosError(): AxiosError {
508
- return {
509
- isAxiosError: true,
510
- name: 'AxiosError',
511
- message: 'Request failed with status code 400',
512
- config: {}, // can be empty for test
513
- toJSON: () => ({}),
514
- response: {
515
- status: 400,
516
- statusText: 'Bad Request',
517
- headers: {},
518
- config: {},
519
- data: {
520
- error: 'Invalid input',
521
- details: ['Field X is required'],
522
- },
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
523
320
  },
524
- } as AxiosError
525
- }
526
- const axiosError = makeFakeAxiosError()
527
- if (!axiosError) {
528
- throw new Error('axiosError is undefined')
529
- }
530
- expect(isAxiosError(axiosError)).toBe(true)
531
- const error0 = Error0.from(axiosError)
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
- `)
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)
540
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
+ // })
541
378
  })