Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

interceptors: move throwOnError to interceptor #3331

Merged
merged 28 commits into from
Jul 11, 2024
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
4192b38
interceptors: move throwOnError to interceptor
Jun 16, 2024
e2321ec
delete http-errors
Jun 17, 2024
7855225
Update response-error.js
mertcanaltin Jun 17, 2024
ea068eb
Update response-error.js
mertcanaltin Jun 17, 2024
6782734
Update lib/interceptor/response-error.js
mertcanaltin Jun 17, 2024
eb29a2a
feat: added new undiciError
Jun 19, 2024
75fcf70
feat: enable interceptor by default
Jun 19, 2024
b2110d4
feat: always throw error when interceptor is used
Jun 19, 2024
e7512d3
feat: add option to throw error on specific status codes
Jun 19, 2024
2195af1
feat: export retry interceptor in index.js
Jun 19, 2024
124e0db
Update response-error.js
mertcanaltin Jun 20, 2024
d731d9f
Update response-error.js
mertcanaltin Jun 21, 2024
b0700c3
Update response-error.js
mertcanaltin Jun 21, 2024
821ea1d
Update response-error.js
mertcanaltin Jun 21, 2024
463f9a5
Update response-error.js
mertcanaltin Jun 21, 2024
a8c3149
Update response-error.js
mertcanaltin Jun 21, 2024
f5adfb2
Update lib/api/index.js
mertcanaltin Jul 1, 2024
a1b282f
lint & added more test
Jul 1, 2024
43a1b07
Update response-error.js
mertcanaltin Jul 1, 2024
0d6f450
Update response-error.js
mertcanaltin Jul 1, 2024
8980063
Update response-error.js
mertcanaltin Jul 1, 2024
636dd26
fix: test repair & unused values delete
Jul 1, 2024
72cea91
Merge branch 'main' of /~https://github.com/mertcanaltin/undici into ad…
Jul 8, 2024
ab619ec
fix: test problem solved
Jul 8, 2024
d5b2eb0
added types
Jul 9, 2024
e0dd09d
added Interceptor info in md
Jul 10, 2024
f10fc30
repair doc
Jul 11, 2024
ccd83e6
Update lib/interceptor/response-error.js
mertcanaltin Jul 11, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions lib/api/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,6 @@ module.exports.stream = require('./api-stream')
module.exports.pipeline = require('./api-pipeline')
module.exports.upgrade = require('./api-upgrade')
module.exports.connect = require('./api-connect')

module.exports.responseErrorInterceptor = require('../interceptor/response-error')
module.exports.retryInterceptor = require('../interceptor/retry')
mertcanaltin marked this conversation as resolved.
Show resolved Hide resolved
13 changes: 13 additions & 0 deletions lib/core/errors.js
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,18 @@ class RequestRetryError extends UndiciError {
}
}

class ResponseError extends UndiciError {
constructor (message, code, { headers, data }) {
super(message)
this.name = 'ResponseError'
this.message = message || 'Response error'
this.code = 'UND_ERR_RESPONSE'
this.statusCode = code
this.data = data
this.headers = headers
}
}

class SecureProxyConnectionError extends UndiciError {
constructor (cause, message, options) {
super(message, { cause, ...(options ?? {}) })
Expand Down Expand Up @@ -227,5 +239,6 @@ module.exports = {
BalancedPoolMissingUpstreamError,
ResponseExceededMaxSizeError,
RequestRetryError,
ResponseError,
SecureProxyConnectionError
}
87 changes: 87 additions & 0 deletions lib/interceptor/response-error.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
'use strict'

const { parseHeaders } = require('../core/util')
const { DecoratorHandler } = require('undici')
mertcanaltin marked this conversation as resolved.
Show resolved Hide resolved
const { ResponseError } = require('../core/errors')

class Handler extends DecoratorHandler {
#handler
#statusCode
#contentType
#decoder
#headers
#body
#errored
mertcanaltin marked this conversation as resolved.
Show resolved Hide resolved
mertcanaltin marked this conversation as resolved.
Show resolved Hide resolved

constructor (opts, { handler }) {
super(handler)
this.#handler = handler
this.opts = opts
mertcanaltin marked this conversation as resolved.
Show resolved Hide resolved
}

onConnect (abort) {
this.#statusCode = 0
this.#contentType = null
this.#decoder = null
this.#headers = null
this.#body = ''
this.#errored = false
mertcanaltin marked this conversation as resolved.
Show resolved Hide resolved

return this.#handler.onConnect(abort)
}

onHeaders (statusCode, rawHeaders, resume, statusMessage, headers = parseHeaders(rawHeaders)) {
mertcanaltin marked this conversation as resolved.
Show resolved Hide resolved
this.#statusCode = statusCode
this.#headers = headers
this.#contentType = headers['content-type']

if (this.#statusCode < 400) {
return this.#handler.onHeaders(statusCode, rawHeaders, resume, statusMessage, headers)
}

if (this.#contentType === 'application/json' || this.#contentType === 'text/plain') {
this.#decoder = new TextDecoder('utf-8')
}
mertcanaltin marked this conversation as resolved.
Show resolved Hide resolved
}

onData (chunk) {
if (this.#statusCode >= 400) {
this.#body += this.#decoder?.decode(chunk, { stream: true }) ?? ''
} else {
return this.#handler.onData(chunk)
}
mertcanaltin marked this conversation as resolved.
Show resolved Hide resolved
}
mertcanaltin marked this conversation as resolved.
Show resolved Hide resolved

onComplete (rawTrailers) {
if (this.#statusCode >= 400) {
mertcanaltin marked this conversation as resolved.
Show resolved Hide resolved
this.#body += this.#decoder?.decode(undefined, { stream: false }) ?? ''

if (this.#contentType === 'application/json') {
try {
this.#body = JSON.parse(this.#body)
} catch {
// Do nothing...
}
}

this.#errored = true
mertcanaltin marked this conversation as resolved.
Show resolved Hide resolved

const err = new ResponseError('Response Error', this.#statusCode, this.#headers, this.#body)
mertcanaltin marked this conversation as resolved.
Show resolved Hide resolved

if (this.opts.throwOnError !== false && (this.opts.error !== false || (Array.isArray(this.opts.statusCodes) && this.opts.statusCodes.includes(this.#statusCode)))) {
mertcanaltin marked this conversation as resolved.
Show resolved Hide resolved
this.#handler.onError(err)
} else {
this.#handler.onComplete(rawTrailers)
}
} else {
this.#handler.onComplete(rawTrailers)
}
}

onError (err) {
this.#handler.onError(err)
}
}

module.exports = (dispatch) => (opts, handler) =>
dispatch(opts, new Handler(opts, { handler }))
mertcanaltin marked this conversation as resolved.
Show resolved Hide resolved
18 changes: 18 additions & 0 deletions test/interceptors/response-error.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
'use strict'

const assert = require('assert')
const { test } = require('node:test')
const createResponseErrorInterceptor = require('../../lib/interceptor/response-error')

test('should not error if request is not meant to be retried', async (t) => {
mertcanaltin marked this conversation as resolved.
Show resolved Hide resolved
const response = { statusCode: 400 }
const handler = {
onError: () => {},
onData: () => {},
onComplete: () => {}
}

const interceptor = createResponseErrorInterceptor((opts, handler) => handler.onComplete())

assert.doesNotThrow(() => interceptor({ response, throwOnError: false }, handler))
})
Loading