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

fix: add jsdoc and do minor changes in utils.js #3550

Merged
merged 1 commit into from
Sep 5, 2024
Merged
Changes from all commits
Commits
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
168 changes: 148 additions & 20 deletions lib/core/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ class BodyAsyncIterable {
}
}

/**
* @param {*} body
* @returns {*}
*/
function wrapRequestBody (body) {
if (isStream(body)) {
// TODO (fix): Provide some way for the user to cache the file to e.g. /tmp
Expand Down Expand Up @@ -67,11 +71,19 @@ function wrapRequestBody (body) {
}
}

/**
* @param {*} obj
* @returns {obj is import('node:stream').Stream}
*/
function isStream (obj) {
return obj && typeof obj === 'object' && typeof obj.pipe === 'function' && typeof obj.on === 'function'
}

// based on https://github.com/node-fetch/fetch-blob/blob/8ab587d34080de94140b54f07168451e7d0b655e/index.js#L229-L241 (MIT License)
/**
* @param {*} object
* @returns {object is Blob}
* based on https://github.com/node-fetch/fetch-blob/blob/8ab587d34080de94140b54f07168451e7d0b655e/index.js#L229-L241 (MIT License)
*/
function isBlobLike (object) {
if (object === null) {
return false
Expand Down Expand Up @@ -108,6 +120,10 @@ function serializePathWithQuery (url, queryParams) {
return url
}

/**
* @param {number|string|undefined} port
* @returns {boolean}
*/
function isValidPort (port) {
const value = parseInt(port, 10)
return (
Expand All @@ -117,6 +133,12 @@ function isValidPort (port) {
)
}

/**
* Check if the value is a valid http or https prefixed string.
*
* @param {string} value
* @returns {boolean}
*/
function isHttpOrHttpsPrefixed (value) {
return (
value != null &&
Expand All @@ -134,8 +156,15 @@ function isHttpOrHttpsPrefixed (value) {
)
}

/**
* @param {string|URL|Record<string,string>} url
* @returns {URL}
*/
function parseURL (url) {
if (typeof url === 'string') {
/**
* @type {URL}
*/
url = new URL(url)

if (!isHttpOrHttpsPrefixed(url.origin || url.protocol)) {
Expand Down Expand Up @@ -205,6 +234,10 @@ function parseURL (url) {
return url
}

/**
* @param {string|URL|Record<string, string>} url
* @returns {URL}
*/
function parseOrigin (url) {
url = parseURL(url)

Expand All @@ -215,6 +248,10 @@ function parseOrigin (url) {
return url
}

/**
* @param {string} host
* @returns {string}
*/
function getHostname (host) {
if (host[0] === '[') {
const idx = host.indexOf(']')
Expand All @@ -229,8 +266,12 @@ function getHostname (host) {
return host.substring(0, idx)
}

// IP addresses are not valid server names per RFC6066
// > Currently, the only server names supported are DNS hostnames
/**
* IP addresses are not valid server names per RFC6066
* Currently, the only server names supported are DNS hostnames
* @param {string|null} host
* @returns {string|null}
*/
function getServerName (host) {
if (!host) {
return null
Expand All @@ -246,18 +287,34 @@ function getServerName (host) {
return servername
}

/**
* @param {*} obj
* @returns {*}
*/
function deepClone (obj) {
return JSON.parse(JSON.stringify(obj))
}

/**
* @param {*} obj
* @returns {obj is AsyncIterable}
*/
function isAsyncIterable (obj) {
return !!(obj != null && typeof obj[Symbol.asyncIterator] === 'function')
}

/**
* @param {*} obj
* @returns {obj is Iterable}
*/
function isIterable (obj) {
return !!(obj != null && (typeof obj[Symbol.iterator] === 'function' || typeof obj[Symbol.asyncIterator] === 'function'))
}

/**
* @param {Blob|Buffer|import ('stream').Stream} body
* @returns {number|null}
*/
function bodyLength (body) {
if (body == null) {
return 0
Expand All @@ -275,10 +332,19 @@ function bodyLength (body) {
return null
}

/**
* @param {import ('stream').Stream} body
* @returns {boolean}
*/
function isDestroyed (body) {
return body && !!(body.destroyed || body[kDestroyed] || (stream.isDestroyed?.(body)))
}

/**
* @param {import ('stream').Stream} stream
* @param {Error} [err]
* @returns
*/
function destroy (stream, err) {
if (stream == null || !isStream(stream) || isDestroyed(stream)) {
return
Expand All @@ -303,8 +369,12 @@ function destroy (stream, err) {
}

const KEEPALIVE_TIMEOUT_EXPR = /timeout=(\d+)/
/**
* @param {string} val
* @returns {number | null}
*/
function parseKeepAliveTimeout (val) {
const m = val.toString().match(KEEPALIVE_TIMEOUT_EXPR)
const m = val.match(KEEPALIVE_TIMEOUT_EXPR)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

parseKeepAliveTimeout gets according to my analysis always a string, so we can avoid the toString() call

return m ? parseInt(m[1], 10) * 1000 : null
}

Expand All @@ -329,12 +399,13 @@ function bufferToLowerCasedHeaderName (value) {
}

/**
* @param {Record<string, string | string[]> | (Buffer | string | (Buffer | string)[])[]} headers
* @param {(Buffer | string)[]} headers
* @param {Record<string, string | string[]>} [obj]
* @returns {Record<string, string | string[]>}
*/
function parseHeaders (headers, obj) {
if (obj === undefined) obj = {}

for (let i = 0; i < headers.length; i += 2) {
const key = headerNameToString(headers[i])
let val = obj[key]
Expand Down Expand Up @@ -363,17 +434,24 @@ function parseHeaders (headers, obj) {
return obj
}

/**
* @param {Buffer[]} headers
* @returns {string[]}
*/
function parseRawHeaders (headers) {
const len = headers.length
const ret = new Array(len)
const headersLength = headers.length
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we can reuse the headersLength variable for the for loop

/**
* @type {string[]}
*/
const ret = new Array(headersLength)

let hasContentLength = false
let contentDispositionIdx = -1
let key
let val
let kLen = 0

for (let n = 0; n < headers.length; n += 2) {
for (let n = 0; n < headersLength; n += 2) {
key = headers[n]
val = headers[n + 1]

Expand Down Expand Up @@ -439,13 +517,33 @@ function validateHandler (handler, method, upgrade) {
}
}

// A body is disturbed if it has been read from and it cannot
// be re-used without losing state or data.
/**
* A body is disturbed if it has been read from and it cannot be re-used without
* losing state or data.
* @param {import('node:stream').Readable} body
* @returns {boolean}
*/
function isDisturbed (body) {
// TODO (fix): Why is body[kBodyUsed] needed?
return !!(body && (stream.isDisturbed(body) || body[kBodyUsed]))
}

/**
* @typedef {object} SocketInfo
* @property {string} [localAddress]
* @property {number} [localPort]
* @property {string} [remoteAddress]
* @property {number} [remotePort]
* @property {string} [remoteFamily]
* @property {number} [timeout]
* @property {number} bytesWritten
* @property {number} bytesRead
*/

/**
* @param {import('net').Socket} socket
* @returns {SocketInfo}
*/
function getSocketInfo (socket) {
return {
localAddress: socket.localAddress,
Expand All @@ -459,7 +557,9 @@ function getSocketInfo (socket) {
}
}

/** @type {globalThis['ReadableStream']} */
/**
* @returns {globalThis['ReadableStream']}
*/
function ReadableStreamFrom (iterable) {
// We cannot use ReadableStream.from here because it does not return a byte stream.

Expand All @@ -484,16 +584,20 @@ function ReadableStreamFrom (iterable) {
}
return controller.desiredSize > 0
},
async cancel (reason) {
async cancel () {
await iterator.return()
},
type: 'bytes'
}
)
}

// The chunk should be a FormData instance and contains
// all the required methods.
/**
* The object should be a FormData instance and contains all the required
* methods.
* @param {*} object
* @returns {object is FormData}
*/
function isFormDataLike (object) {
return (
object &&
Expand Down Expand Up @@ -538,6 +642,7 @@ function isUSVString (val) {
/**
* @see https://tools.ietf.org/html/rfc7230#section-3.2.6
* @param {number} c
* @returns {boolean}
*/
function isTokenCharCode (c) {
switch (c) {
Expand Down Expand Up @@ -568,6 +673,7 @@ function isTokenCharCode (c) {

/**
* @param {string} characters
* @returns {boolean}
*/
function isValidHTTPToken (characters) {
if (characters.length === 0) {
Expand All @@ -594,17 +700,24 @@ const headerCharRegex = /[^\t\x20-\x7e\x80-\xff]/

/**
* @param {string} characters
* @returns {boolean}
*/
function isValidHeaderValue (characters) {
return !headerCharRegex.test(characters)
}

// Parsed accordingly to RFC 9110
// https://www.rfc-editor.org/rfc/rfc9110#field.content-range
const rangeHeaderRegex = /^bytes (\d+)-(\d+)\/(\d+)?$/

/**
* Parse accordingly to RFC 9110
* @see https://www.rfc-editor.org/rfc/rfc9110#field.content-range
* @param {string} [range]
* @returns
*/
function parseRangeHeader (range) {
if (range == null || range === '') return { start: 0, end: null, size: null }

const m = range ? range.match(/^bytes (\d+)-(\d+)\/(\d+)?$/) : null
const m = range ? range.match(rangeHeaderRegex) : null
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

extracted the range regex for better perf

return m
? {
start: parseInt(m[1]),
Expand All @@ -614,20 +727,35 @@ function parseRangeHeader (range) {
: null
}

/**
* @param {Record<string|symbol, any>} obj
* @param {string} name
* @param {Function} listener
*/
function addListener (obj, name, listener) {
const listeners = (obj[kListeners] ??= [])
listeners.push([name, listener])
obj.on(name, listener)
return obj
}

/**
* @param {Record<string|symbol, any>} obj
*/
function removeAllListeners (obj) {
for (const [name, listener] of obj[kListeners] ?? []) {
obj.removeListener(name, listener)
if (obj[kListeners] != null) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if obj[kListeners] is not null or undefined, then we know it is an array, so only do the steps if we have somthing set.

for (const [name, listener] of obj[kListeners]) {
obj.removeListener(name, listener)
}
obj[kListeners] = null
}
obj[kListeners] = null
}

/**
* @param {import ('../dispatcher/client')} client
* @param {import ('../core/request')} request
* @param {Error} err
*/
function errorRequest (client, request, err) {
try {
request.onError(err)
Expand Down
Loading