1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-11-27 16:10:57 -05:00
denoland-deno/ext/node/polyfills/internal/validators.mjs
Nathan Whitaker 219a27dde5
fix(ext/node): Support returning tokens and option defaults in node:util.parseArgs (#23192)
Fixes #23179.
Fixes #22454.

Enables passing `{tokens: true}` to `parseArgs` and setting default
values for options.

With this PR, the observable framework works with deno out of the box
(no unstable flags needed).

The existing code was basically copied straight from node, so this PR
mostly just updates that (out of date) vendored code. Also fixes some
issues with error exports (before this PR, in certain error cases we
were attempting to construct error classes that weren't actually in
scope).

The last change (in the second commit) adds a small hack so that we
actually exercise the `test-parse-args.js` node_compat test, previously
it was reported as passing though it should have failed. That test now
passes.

---------

Co-authored-by: Bartek Iwańczuk <biwanczuk@gmail.com>
2024-04-02 16:20:48 -07:00

384 lines
9.6 KiB
JavaScript

// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
// Copyright Joyent and Node contributors. All rights reserved. MIT license.
// TODO(petamoriken): enable prefer-primordials for node polyfills
// deno-lint-ignore-file prefer-primordials
import { primordials } from "ext:core/mod.js";
const {
ArrayPrototypeIncludes,
ArrayPrototypeJoin,
} = primordials;
import { codes } from "ext:deno_node/internal/error_codes.ts";
import { hideStackFrames } from "ext:deno_node/internal/hide_stack_frames.ts";
import { isArrayBufferView } from "ext:deno_node/internal/util/types.ts";
import { normalizeEncoding } from "ext:deno_node/internal/normalize_encoding.mjs";
/**
* @param {number} value
* @returns {boolean}
*/
function isInt32(value) {
return value === (value | 0);
}
/**
* @param {unknown} value
* @returns {boolean}
*/
function isUint32(value) {
return value === (value >>> 0);
}
const octalReg = /^[0-7]+$/;
const modeDesc = "must be a 32-bit unsigned integer or an octal string";
/**
* Parse and validate values that will be converted into mode_t (the S_*
* constants). Only valid numbers and octal strings are allowed. They could be
* converted to 32-bit unsigned integers or non-negative signed integers in the
* C++ land, but any value higher than 0o777 will result in platform-specific
* behaviors.
*
* @param {*} value Values to be validated
* @param {string} name Name of the argument
* @param {number} [def] If specified, will be returned for invalid values
* @returns {number}
*/
function parseFileMode(value, name, def) {
value ??= def;
if (typeof value === "string") {
if (!octalReg.test(value)) {
throw new codes.ERR_INVALID_ARG_VALUE(name, value, modeDesc);
}
value = Number.parseInt(value, 8);
}
validateInt32(value, name, 0, 2 ** 32 - 1);
return value;
}
const validateBuffer = hideStackFrames((buffer, name = "buffer") => {
if (!isArrayBufferView(buffer)) {
throw new codes.ERR_INVALID_ARG_TYPE(
name,
["Buffer", "TypedArray", "DataView"],
buffer,
);
}
});
const validateInteger = hideStackFrames(
(
value,
name,
min = Number.MIN_SAFE_INTEGER,
max = Number.MAX_SAFE_INTEGER,
) => {
if (typeof value !== "number") {
throw new codes.ERR_INVALID_ARG_TYPE(name, "number", value);
}
if (!Number.isInteger(value)) {
throw new codes.ERR_OUT_OF_RANGE(name, "an integer", value);
}
if (value < min || value > max) {
throw new codes.ERR_OUT_OF_RANGE(name, `>= ${min} && <= ${max}`, value);
}
},
);
/**
* @param {unknown} value
* @param {string} name
* @param {{
* allowArray?: boolean,
* allowFunction?: boolean,
* nullable?: boolean
* }} [options]
*/
const validateObject = hideStackFrames((value, name, options) => {
const useDefaultOptions = options == null;
const allowArray = useDefaultOptions ? false : options.allowArray;
const allowFunction = useDefaultOptions ? false : options.allowFunction;
const nullable = useDefaultOptions ? false : options.nullable;
if (
(!nullable && value === null) ||
(!allowArray && Array.isArray(value)) ||
(typeof value !== "object" && (
!allowFunction || typeof value !== "function"
))
) {
throw new codes.ERR_INVALID_ARG_TYPE(name, "Object", value);
}
});
const validateInt32 = hideStackFrames(
(value, name, min = -2147483648, max = 2147483647) => {
// The defaults for min and max correspond to the limits of 32-bit integers.
if (!isInt32(value)) {
if (typeof value !== "number") {
throw new codes.ERR_INVALID_ARG_TYPE(name, "number", value);
}
if (!Number.isInteger(value)) {
throw new codes.ERR_OUT_OF_RANGE(name, "an integer", value);
}
throw new codes.ERR_OUT_OF_RANGE(name, `>= ${min} && <= ${max}`, value);
}
if (value < min || value > max) {
throw new codes.ERR_OUT_OF_RANGE(name, `>= ${min} && <= ${max}`, value);
}
},
);
const validateUint32 = hideStackFrames(
(value, name, positive) => {
if (!isUint32(value)) {
if (typeof value !== "number") {
throw new codes.ERR_INVALID_ARG_TYPE(name, "number", value);
}
if (!Number.isInteger(value)) {
throw new codes.ERR_OUT_OF_RANGE(name, "an integer", value);
}
const min = positive ? 1 : 0;
// 2 ** 32 === 4294967296
throw new codes.ERR_OUT_OF_RANGE(
name,
`>= ${min} && < 4294967296`,
value,
);
}
if (positive && value === 0) {
throw new codes.ERR_OUT_OF_RANGE(name, ">= 1 && < 4294967296", value);
}
},
);
/**
* @param {unknown} value
* @param {string} name
*/
function validateString(value, name) {
if (typeof value !== "string") {
throw new codes.ERR_INVALID_ARG_TYPE(name, "string", value);
}
}
/**
* @param {unknown} value
* @param {string} name
*/
function validateNumber(value, name) {
if (typeof value !== "number") {
throw new codes.ERR_INVALID_ARG_TYPE(name, "number", value);
}
}
/**
* @param {unknown} value
* @param {string} name
*/
function validateBoolean(value, name) {
if (typeof value !== "boolean") {
throw new codes.ERR_INVALID_ARG_TYPE(name, "boolean", value);
}
}
/**
* @param {unknown} value
* @param {string} name
* @param {unknown[]} oneOf
*/
const validateOneOf = hideStackFrames(
(value, name, oneOf) => {
if (!Array.prototype.includes.call(oneOf, value)) {
const allowed = Array.prototype.join.call(
Array.prototype.map.call(
oneOf,
(v) => (typeof v === "string" ? `'${v}'` : String(v)),
),
", ",
);
const reason = "must be one of: " + allowed;
throw new codes.ERR_INVALID_ARG_VALUE(name, value, reason);
}
},
);
export function validateEncoding(data, encoding) {
const normalizedEncoding = normalizeEncoding(encoding);
const length = data.length;
if (normalizedEncoding === "hex" && length % 2 !== 0) {
throw new codes.ERR_INVALID_ARG_VALUE(
"encoding",
encoding,
`is invalid for data of length ${length}`,
);
}
}
// Check that the port number is not NaN when coerced to a number,
// is an integer and that it falls within the legal range of port numbers.
/**
* @param {string} name
* @returns {number}
*/
function validatePort(port, name = "Port", allowZero = true) {
if (
(typeof port !== "number" && typeof port !== "string") ||
(typeof port === "string" &&
String.prototype.trim.call(port).length === 0) ||
+port !== (+port >>> 0) ||
port > 0xFFFF ||
(port === 0 && !allowZero)
) {
throw new codes.ERR_SOCKET_BAD_PORT(name, port, allowZero);
}
return port;
}
/**
* @param {unknown} signal
* @param {string} name
*/
const validateAbortSignal = hideStackFrames(
(signal, name) => {
if (
signal !== undefined &&
(signal === null ||
typeof signal !== "object" ||
!("aborted" in signal))
) {
throw new codes.ERR_INVALID_ARG_TYPE(name, "AbortSignal", signal);
}
},
);
/**
* @param {unknown} value
* @param {string} name
*/
const validateFunction = hideStackFrames(
(value, name) => {
if (typeof value !== "function") {
throw new codes.ERR_INVALID_ARG_TYPE(name, "Function", value);
}
},
);
/**
* @param {unknown} value
* @param {string} name
*/
const validateArray = hideStackFrames(
(value, name, minLength = 0) => {
if (!Array.isArray(value)) {
throw new codes.ERR_INVALID_ARG_TYPE(name, "Array", value);
}
if (value.length < minLength) {
const reason = `must be longer than ${minLength}`;
throw new codes.ERR_INVALID_ARG_VALUE(name, value, reason);
}
},
);
/**
* @callback validateStringArray
* @param {*} value
* @param {string} name
* @returns {asserts value is string[]}
*/
/** @type {validateStringArray} */
const validateStringArray = hideStackFrames((value, name) => {
validateArray(value, name);
for (let i = 0; i < value.length; ++i) {
// Don't use validateString here for performance reasons, as
// we would generate intermediate strings for the name.
if (typeof value[i] !== "string") {
throw new codes.ERR_INVALID_ARG_TYPE(`${name}[${i}]`, "string", value[i]);
}
}
});
/**
* @callback validateBooleanArray
* @param {*} value
* @param {string} name
* @returns {asserts value is boolean[]}
*/
/** @type {validateBooleanArray} */
const validateBooleanArray = hideStackFrames((value, name) => {
validateArray(value, name);
for (let i = 0; i < value.length; ++i) {
// Don't use validateBoolean here for performance reasons, as
// we would generate intermediate strings for the name.
if (value[i] !== true && value[i] !== false) {
throw new codes.ERR_INVALID_ARG_TYPE(
`${name}[${i}]`,
"boolean",
value[i],
);
}
}
});
function validateUnion(value, name, union) {
if (!ArrayPrototypeIncludes(union, value)) {
throw new codes.ERR_INVALID_ARG_TYPE(
name,
`('${ArrayPrototypeJoin(union, "|")}')`,
value,
);
}
}
export default {
isInt32,
isUint32,
parseFileMode,
validateAbortSignal,
validateArray,
validateBoolean,
validateBooleanArray,
validateBuffer,
validateFunction,
validateInt32,
validateInteger,
validateNumber,
validateObject,
validateOneOf,
validatePort,
validateString,
validateStringArray,
validateUint32,
validateUnion,
};
export {
isInt32,
isUint32,
parseFileMode,
validateAbortSignal,
validateArray,
validateBoolean,
validateBooleanArray,
validateBuffer,
validateFunction,
validateInt32,
validateInteger,
validateNumber,
validateObject,
validateOneOf,
validatePort,
validateString,
validateStringArray,
validateUint32,
validateUnion,
};