@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/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,340 @@ 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
- }
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 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":
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('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,
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('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',
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
- 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
- },
167
- },
168
- })
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
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('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
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('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
- `)
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('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
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('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
- `)
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('expected', () => {
355
- const error0 = new Error0({
356
- expected: true,
357
- })
358
- expect(error0.expected).toBe(true)
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
- const error1 = new Error0({
361
- expected: false,
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
- expect(error1.expected).toBe(false)
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
- const error3 = new Error0({
366
- expected: true,
367
- cause: error0,
368
- })
369
- expect(error3.expected).toBe(true)
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
- const error4 = new Error0({
372
- expected: false,
373
- cause: error0,
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
- expect(error4.expected).toBe(false)
194
+ const error = new AppError('test')
195
+ const json = AppError.serialize(error)
196
+ expect('stack' in json).toBe(false)
197
+ })
376
198
 
377
- const error5 = new Error0({
378
- expected: true,
379
- cause: error1,
380
- })
381
- expect(error5.expected).toBe(false)
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
- const error6 = new Error0({
384
- expected: false,
385
- cause: error1,
386
- })
387
- expect(error6.expected).toBe(false)
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('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
- `)
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('extend collection', () => {
417
- const e0s1 = Error0.extendCollection(e0s, {
418
- defaultMessage: 'nested error',
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 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
- `)
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('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')
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 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"
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('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
- },
523
- },
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
- `)
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
  })