Skip to content

Commit f9997bb

Browse files
authored
fix: add missing error classes to types (#3316)
* add missing error classes to types * test new types * test error includes data with count when etag mismatch * test content-range mismatch error * remove test debugging
1 parent 78a0c24 commit f9997bb

File tree

4 files changed

+141
-5
lines changed

4 files changed

+141
-5
lines changed

lib/handler/retry-handler.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,7 @@ class RetryHandler {
202202
this.abort(
203203
new RequestRetryError('Content-Range mismatch', statusCode, {
204204
headers,
205-
count: this.retryCount
205+
data: { count: this.retryCount }
206206
})
207207
)
208208
return false
@@ -213,7 +213,7 @@ class RetryHandler {
213213
this.abort(
214214
new RequestRetryError('ETag mismatch', statusCode, {
215215
headers,
216-
count: this.retryCount
216+
data: { count: this.retryCount }
217217
})
218218
)
219219
return false

test/retry-handler.js

Lines changed: 108 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -595,7 +595,7 @@ test('Should handle 206 partial content', async t => {
595595
onBodySent () {
596596
t.ok(true, 'pass')
597597
},
598-
onHeaders (status, _rawHeaders, resume, _statusMessage) {
598+
onHeaders (status, _rawHeaders, _resume, _statusMessage) {
599599
t.strictEqual(status, 200)
600600
return true
601601
},
@@ -636,7 +636,7 @@ test('Should handle 206 partial content', async t => {
636636
})
637637

638638
test('Should handle 206 partial content - bad-etag', async t => {
639-
t = tspl(t, { plan: 6 })
639+
t = tspl(t, { plan: 8 })
640640

641641
const chunks = []
642642

@@ -683,7 +683,7 @@ test('Should handle 206 partial content - bad-etag', async t => {
683683
onBodySent () {
684684
t.ok(true, 'pass')
685685
},
686-
onHeaders (status, _rawHeaders, resume, _statusMessage) {
686+
onHeaders (_status, _rawHeaders, _resume, _statusMessage) {
687687
t.ok(true, 'pass')
688688
return true
689689
},
@@ -697,6 +697,8 @@ test('Should handle 206 partial content - bad-etag', async t => {
697697
onError (err) {
698698
t.strictEqual(Buffer.concat(chunks).toString('utf-8'), 'abc')
699699
t.strictEqual(err.code, 'UND_ERR_REQ_RETRY')
700+
t.strictEqual(err.message, 'ETag mismatch')
701+
t.deepEqual(err.data, { count: 2 })
700702
}
701703
}
702704
}
@@ -1083,6 +1085,7 @@ test('Should be able to properly pass the minTimeout to the RetryContext when co
10831085

10841086
await t.completed
10851087
})
1088+
10861089
test('Issue#2986 - Handle custom 206', async t => {
10871090
t = tspl(t, { plan: 8 })
10881091

@@ -1504,3 +1507,105 @@ test('Weak etags are ignored on range-requests', async t => {
15041507

15051508
await t.completed
15061509
})
1510+
1511+
test('Should throw RequestRetryError when Content-Range mismatch', async t => {
1512+
t = tspl(t, { plan: 10 })
1513+
1514+
const chunks = []
1515+
1516+
// Took from: https://github.com/nxtedition/nxt-lib/blob/4b001ebc2f22cf735a398f35ff800dd553fe5933/test/undici/retry.js#L47
1517+
let x = 0
1518+
const server = createServer((req, res) => {
1519+
if (x === 0) {
1520+
t.ok(true, 'pass')
1521+
res.setHeader('etag', 'asd')
1522+
res.write('abc')
1523+
setTimeout(() => {
1524+
res.destroy()
1525+
}, 1e2)
1526+
} else if (x === 1) {
1527+
t.deepStrictEqual(req.headers.range, 'bytes=3-')
1528+
res.setHeader('content-range', 'bytes bad') // intentionally bad to trigger error
1529+
res.setHeader('etag', 'asd')
1530+
res.statusCode = 206
1531+
res.end('def')
1532+
}
1533+
x++
1534+
})
1535+
1536+
const dispatchOptions = {
1537+
retryOptions: {
1538+
retry: function (err, _, done) {
1539+
if (err.code && err.code === 'UND_ERR_DESTROYED') {
1540+
return done(false)
1541+
}
1542+
1543+
if (err.statusCode === 206) return done(err)
1544+
1545+
setTimeout(done, 800)
1546+
}
1547+
},
1548+
method: 'GET',
1549+
path: '/',
1550+
headers: {
1551+
'content-type': 'application/json'
1552+
}
1553+
}
1554+
1555+
server.listen(0, () => {
1556+
const client = new Client(`http://localhost:${server.address().port}`)
1557+
const handler = new RetryHandler(dispatchOptions, {
1558+
dispatch: (...args) => {
1559+
return client.dispatch(...args)
1560+
},
1561+
handler: {
1562+
onRequestSent () {
1563+
t.ok(true, 'pass')
1564+
},
1565+
onConnect () {
1566+
t.ok(true, 'pass')
1567+
},
1568+
onBodySent () {
1569+
t.ok(true, 'pass')
1570+
},
1571+
onHeaders (status, _rawHeaders, _resume, _statusMessage) {
1572+
t.strictEqual(status, 200)
1573+
return true
1574+
},
1575+
onData (chunk) {
1576+
chunks.push(chunk)
1577+
return true
1578+
},
1579+
onComplete () {
1580+
t.ifError('should not complete')
1581+
},
1582+
onError (err) {
1583+
t.strictEqual(Buffer.concat(chunks).toString('utf-8'), 'abc')
1584+
t.strictEqual(err.code, 'UND_ERR_REQ_RETRY')
1585+
t.strictEqual(err.message, 'Content-Range mismatch')
1586+
t.deepEqual(err.data, { count: 2 })
1587+
}
1588+
}
1589+
})
1590+
1591+
client.dispatch(
1592+
{
1593+
method: 'GET',
1594+
path: '/',
1595+
headers: {
1596+
'content-type': 'application/json'
1597+
}
1598+
},
1599+
handler
1600+
)
1601+
1602+
after(async () => {
1603+
await client.close()
1604+
1605+
server.close()
1606+
await once(server, 'close')
1607+
})
1608+
})
1609+
1610+
await t.completed
1611+
})

test/types/errors.test-d.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,16 @@ expectAssignable<errors.ResponseExceededMaxSizeError>(new errors.ResponseExceede
101101
expectAssignable<'ResponseExceededMaxSizeError'>(new errors.ResponseExceededMaxSizeError().name)
102102
expectAssignable<'UND_ERR_RES_EXCEEDED_MAX_SIZE'>(new errors.ResponseExceededMaxSizeError().code)
103103

104+
expectAssignable<errors.UndiciError>(new errors.RequestRetryError('', 0))
105+
expectAssignable<errors.RequestRetryError>(new errors.RequestRetryError('', 0))
106+
expectAssignable<'RequestRetryError'>(new errors.RequestRetryError('', 0).name)
107+
expectAssignable<'UND_ERR_REQ_RETRY'>(new errors.RequestRetryError('', 0).code)
108+
109+
expectAssignable<errors.UndiciError>(new errors.SecureProxyConnectionError())
110+
expectAssignable<errors.SecureProxyConnectionError>(new errors.SecureProxyConnectionError())
111+
expectAssignable<'SecureProxyConnectionError'>(new errors.SecureProxyConnectionError().name)
112+
expectAssignable<'UND_ERR_PRX_TLS'>(new errors.SecureProxyConnectionError().code)
113+
104114
{
105115
// @ts-ignore
106116
function f (): errors.HeadersTimeoutError | errors.ConnectTimeoutError { return }

types/errors.d.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,4 +125,25 @@ declare namespace Errors {
125125
name: 'ResponseExceededMaxSizeError';
126126
code: 'UND_ERR_RES_EXCEEDED_MAX_SIZE';
127127
}
128+
129+
export class RequestRetryError extends UndiciError {
130+
constructor (
131+
message: string,
132+
statusCode: number,
133+
headers?: IncomingHttpHeaders | string[] | null,
134+
body?: null | Record<string, any> | string
135+
);
136+
name: 'RequestRetryError';
137+
code: 'UND_ERR_REQ_RETRY';
138+
statusCode: number;
139+
data: {
140+
count: number;
141+
};
142+
headers: Record<string, string | string[]>;
143+
}
144+
145+
export class SecureProxyConnectionError extends UndiciError {
146+
name: 'SecureProxyConnectionError';
147+
code: 'UND_ERR_PRX_TLS';
148+
}
128149
}

0 commit comments

Comments
 (0)