@devp0nt/error0 1.0.0-next.40 → 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
@@ -22,35 +22,21 @@ const fixStack = (stack: string | undefined) => {
22
22
  describe('Error0', () => {
23
23
  const statusExtension = Error0.extension()
24
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
34
- },
35
- serialize: (value) => value,
25
+ init: (input: number) => input,
26
+ resolve: ({ flow }) => flow.find(Boolean),
27
+ serialize: ({ value }) => value,
28
+ deserialize: ({ value }) => (typeof value === 'number' ? value : undefined),
36
29
  })
37
30
  .method('isStatus', (error, status: number) => error.status === status)
38
31
 
39
32
  const codes = ['NOT_FOUND', 'BAD_REQUEST', 'UNAUTHORIZED'] as const
40
33
  type Code = (typeof codes)[number]
41
34
  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
- }
50
- }
51
- return undefined
52
- },
53
- serialize: (value, error, isPublic) => (isPublic ? undefined : value),
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,
54
40
  })
55
41
 
56
42
  it('simple', () => {
@@ -68,17 +54,10 @@ describe('Error0', () => {
68
54
 
69
55
  it('with direct prop extension', () => {
70
56
  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
80
- },
81
- serialize: (value: number | undefined) => value,
57
+ init: (input: number) => input,
58
+ resolve: ({ flow }) => flow.find(Boolean),
59
+ serialize: ({ value }) => value,
60
+ deserialize: ({ value }) => (typeof value === 'number' ? value : undefined),
82
61
  })
83
62
  const error = new AppError('test', { status: 400 })
84
63
  expect(error).toBeInstanceOf(AppError)
@@ -94,6 +73,30 @@ describe('Error0', () => {
94
73
  expectTypeOf<typeof AppError>().toExtend<ClassError0>()
95
74
  })
96
75
 
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
81
+ },
82
+ serialize: ({ value }) => value,
83
+ deserialize: ({ value }) => (typeof value === 'number' ? value : undefined),
84
+ })
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>()
98
+ })
99
+
97
100
  it('with defined extension', () => {
98
101
  const AppError = Error0.extend(statusExtension)
99
102
  const error = new AppError('test', { status: 400 })
@@ -162,20 +165,14 @@ describe('Error0', () => {
162
165
  })
163
166
 
164
167
  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
- },
168
+ const AppError = Error0.extend(statusExtension).prop('code', {
169
+ init: (input: string) => input,
170
+ resolve: ({ flow }) => flow.find(Boolean),
175
171
  serialize: () => undefined,
172
+ deserialize: ({ value }) => (typeof value === 'string' ? value : undefined),
176
173
  })
177
174
  const error = new AppError('test', { status: 401, code: 'secret' })
178
- const json = AppError.serialize(error) as Record<string, unknown>
175
+ const json = AppError.serialize(error)
179
176
  expect(json.status).toBe(401)
180
177
  expect('code' in json).toBe(false)
181
178
  })
@@ -183,24 +180,19 @@ describe('Error0', () => {
183
180
  it('serialize keeps stack by default without stack extension', () => {
184
181
  const AppError = Error0.extend(statusExtension)
185
182
  const error = new AppError('test', { status: 500 })
186
- const json = AppError.serialize(error) as Record<string, unknown>
183
+ const json = AppError.serialize(error)
187
184
  expect(json.stack).toBe(error.stack)
188
185
  })
189
186
 
190
187
  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,
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),
201
193
  })
202
194
  const error = new AppError('test')
203
- const json = AppError.serialize(error) as Record<string, unknown>
195
+ const json = AppError.serialize(error)
204
196
  expect('stack' in json).toBe(false)
205
197
  })
206
198
 
@@ -215,27 +207,21 @@ describe('Error0', () => {
215
207
  expect(AppError.serialize(recreated, false)).toEqual(json)
216
208
  })
217
209
 
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)
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)
232
218
  })
233
219
 
234
220
  it('serialize can hide props for public output', () => {
235
221
  const AppError = Error0.extend(statusExtension).extend(codeExtension)
236
222
  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>
223
+ const privateJson = AppError.serialize(error, false)
224
+ const publicJson = AppError.serialize(error, true)
239
225
  expect(privateJson.code).toBe('NOT_FOUND')
240
226
  expect('code' in publicJson).toBe(false)
241
227
  })
@@ -308,20 +294,14 @@ describe('Error0', () => {
308
294
 
309
295
  it('expected prop can be realized to send or not to send error to your error tracker', () => {
310
296
  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
320
- },
321
- serialize: (value) => value,
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),
322
302
  })
323
- .extend('method', 'isExpected', (error) => {
324
- return error.expected || false
303
+ .method('isExpected', (error) => {
304
+ return error.expected ?? false
325
305
  })
326
306
  const errorExpected = new AppError('test', { status: 400, expected: true })
327
307
  const errorUnexpected = new AppError('test', { status: 400, expected: false })