UNPKG

nstdlib-nightly

Version:

Node.js standard library converted to runtime-agnostic ES modules.

625 lines (555 loc) 16.9 kB
// Source: https://github.com/nodejs/node/blob/65eff1eb/lib/internal/validators.js import { codes as __codes__, hideStackFrames, } from "nstdlib/lib/internal/errors"; import { normalizeEncoding } from "nstdlib/lib/internal/util"; import { isAsyncFunction, isArrayBufferView, } from "nstdlib/lib/internal/util/types"; /* eslint jsdoc/require-jsdoc: "error" */ const { ERR_INVALID_ARG_TYPE: { HideStackFramesError: ERR_INVALID_ARG_TYPE }, ERR_INVALID_ARG_VALUE: { HideStackFramesError: ERR_INVALID_ARG_VALUE }, ERR_OUT_OF_RANGE: { HideStackFramesError: ERR_OUT_OF_RANGE }, ERR_SOCKET_BAD_PORT: { HideStackFramesError: ERR_SOCKET_BAD_PORT }, ERR_UNKNOWN_SIGNAL: { HideStackFramesError: ERR_UNKNOWN_SIGNAL }, } = __codes__; const { signals } = require("binding/constants").os; /** * @param {*} value * @returns {boolean} */ function isInt32(value) { return value === (value | 0); } /** * @param {*} 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 (RegExp.prototype.exec.call(octalReg, value) === null) { throw new ERR_INVALID_ARG_VALUE(name, value, modeDesc); } value = Number.parseInt(value, 8); } validateUint32(value, name); return value; } /** * @callback validateInteger * @param {*} value * @param {string} name * @param {number} [min] * @param {number} [max] * @returns {asserts value is number} */ /** @type {validateInteger} */ const validateInteger = hideStackFrames( ( value, name, min = Number.MIN_SAFE_INTEGER, max = Number.MAX_SAFE_INTEGER, ) => { if (typeof value !== "number") throw new ERR_INVALID_ARG_TYPE(name, "number", value); if (!Number.isInteger(value)) throw new ERR_OUT_OF_RANGE(name, "an integer", value); if (value < min || value > max) throw new ERR_OUT_OF_RANGE(name, `>= ${min} && <= ${max}`, value); }, ); /** * @callback validateInt32 * @param {*} value * @param {string} name * @param {number} [min] * @param {number} [max] * @returns {asserts value is number} */ /** @type {validateInt32} */ const validateInt32 = hideStackFrames( (value, name, min = -2147483648, max = 2147483647) => { // The defaults for min and max correspond to the limits of 32-bit integers. if (typeof value !== "number") { throw new ERR_INVALID_ARG_TYPE(name, "number", value); } if (!Number.isInteger(value)) { throw new ERR_OUT_OF_RANGE(name, "an integer", value); } if (value < min || value > max) { throw new ERR_OUT_OF_RANGE(name, `>= ${min} && <= ${max}`, value); } }, ); /** * @callback validateUint32 * @param {*} value * @param {string} name * @param {boolean} [positive=false] * @returns {asserts value is number} */ /** @type {validateUint32} */ const validateUint32 = hideStackFrames((value, name, positive = false) => { if (typeof value !== "number") { throw new ERR_INVALID_ARG_TYPE(name, "number", value); } if (!Number.isInteger(value)) { throw new ERR_OUT_OF_RANGE(name, "an integer", value); } const min = positive ? 1 : 0; // 2 ** 32 === 4294967296 const max = 4_294_967_295; if (value < min || value > max) { throw new ERR_OUT_OF_RANGE(name, `>= ${min} && <= ${max}`, value); } }); /** * @callback validateString * @param {*} value * @param {string} name * @returns {asserts value is string} */ /** @type {validateString} */ const validateString = hideStackFrames((value, name) => { if (typeof value !== "string") throw new ERR_INVALID_ARG_TYPE(name, "string", value); }); /** * @callback validateNumber * @param {*} value * @param {string} name * @param {number} [min] * @param {number} [max] * @returns {asserts value is number} */ /** @type {validateNumber} */ const validateNumber = hideStackFrames((value, name, min = undefined, max) => { if (typeof value !== "number") throw new ERR_INVALID_ARG_TYPE(name, "number", value); if ( (min != null && value < min) || (max != null && value > max) || ((min != null || max != null) && Number.isNaN(value)) ) { throw new ERR_OUT_OF_RANGE( name, `${min != null ? `>= ${min}` : ""}${min != null && max != null ? " && " : ""}${max != null ? `<= ${max}` : ""}`, value, ); } }); /** * @callback validateOneOf * @template T * @param {T} value * @param {string} name * @param {T[]} oneOf */ /** @type {validateOneOf} */ 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 ERR_INVALID_ARG_VALUE(name, value, reason); } }); /** * @callback validateBoolean * @param {*} value * @param {string} name * @returns {asserts value is boolean} */ /** @type {validateBoolean} */ const validateBoolean = hideStackFrames((value, name) => { if (typeof value !== "boolean") throw new ERR_INVALID_ARG_TYPE(name, "boolean", value); }); const kValidateObjectNone = 0; const kValidateObjectAllowNullable = 1 << 0; const kValidateObjectAllowArray = 1 << 1; const kValidateObjectAllowFunction = 1 << 2; const kValidateObjectAllowObjects = kValidateObjectAllowArray | kValidateObjectAllowFunction; const kValidateObjectAllowObjectsAndNull = kValidateObjectAllowNullable | kValidateObjectAllowArray | kValidateObjectAllowFunction; /** * @callback validateObject * @param {*} value * @param {string} name * @param {number} [options] */ /** @type {validateObject} */ const validateObject = hideStackFrames( (value, name, options = kValidateObjectNone) => { if (options === kValidateObjectNone) { if (value === null || Array.isArray(value)) { throw new ERR_INVALID_ARG_TYPE(name, "Object", value); } if (typeof value !== "object") { throw new ERR_INVALID_ARG_TYPE(name, "Object", value); } } else { const throwOnNullable = (kValidateObjectAllowNullable & options) === 0; if (throwOnNullable && value === null) { throw new ERR_INVALID_ARG_TYPE(name, "Object", value); } const throwOnArray = (kValidateObjectAllowArray & options) === 0; if (throwOnArray && Array.isArray(value)) { throw new ERR_INVALID_ARG_TYPE(name, "Object", value); } const throwOnFunction = (kValidateObjectAllowFunction & options) === 0; const typeofValue = typeof value; if ( typeofValue !== "object" && (throwOnFunction || typeofValue !== "function") ) { throw new ERR_INVALID_ARG_TYPE(name, "Object", value); } } }, ); /** * @callback validateDictionary - We are using the Web IDL Standard definition * of "dictionary" here, which means any value * whose Type is either Undefined, Null, or * Object (which includes functions). * @param {*} value * @param {string} name * @see https://webidl.spec.whatwg.org/#es-dictionary * @see https://tc39.es/ecma262/#table-typeof-operator-results */ /** @type {validateDictionary} */ const validateDictionary = hideStackFrames((value, name) => { if ( value != null && typeof value !== "object" && typeof value !== "function" ) { throw new ERR_INVALID_ARG_TYPE(name, "a dictionary", value); } }); /** * @callback validateArray * @param {*} value * @param {string} name * @param {number} [minLength] * @returns {asserts value is any[]} */ /** @type {validateArray} */ const validateArray = hideStackFrames((value, name, minLength = 0) => { if (!Array.isArray(value)) { throw new ERR_INVALID_ARG_TYPE(name, "Array", value); } if (value.length < minLength) { const reason = `must be longer than ${minLength}`; throw new 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 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 ERR_INVALID_ARG_TYPE(`${name}[${i}]`, "boolean", value[i]); } } }); /** * @callback validateAbortSignalArray * @param {*} value * @param {string} name * @returns {asserts value is AbortSignal[]} */ /** @type {validateAbortSignalArray} */ function validateAbortSignalArray(value, name) { validateArray(value, name); for (let i = 0; i < value.length; i++) { const signal = value[i]; const indexedName = `${name}[${i}]`; if (signal == null) { throw new ERR_INVALID_ARG_TYPE(indexedName, "AbortSignal", signal); } validateAbortSignal(signal, indexedName); } } /** * @param {*} signal * @param {string} [name='signal'] * @returns {asserts signal is keyof signals} */ const validateSignalName = hideStackFrames((signal, name = "signal") => { validateString(signal, name); if (signals[signal] === undefined) { if (signals[String.prototype.toUpperCase.call(signal)] !== undefined) { throw new ERR_UNKNOWN_SIGNAL( signal + " (signals must use all capital letters)", ); } throw new ERR_UNKNOWN_SIGNAL(signal); } }); /** * @callback validateBuffer * @param {*} buffer * @param {string} [name='buffer'] * @returns {asserts buffer is ArrayBufferView} */ /** @type {validateBuffer} */ const validateBuffer = hideStackFrames((buffer, name = "buffer") => { if (!isArrayBufferView(buffer)) { throw new ERR_INVALID_ARG_TYPE( name, ["Buffer", "TypedArray", "DataView"], buffer, ); } }); /** * @param {string} data * @param {string} encoding */ const validateEncoding = hideStackFrames((data, encoding) => { const normalizedEncoding = normalizeEncoding(encoding); const length = data.length; if (normalizedEncoding === "hex" && length % 2 !== 0) { throw new 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 {*} port * @param {string} [name='Port'] * @param {boolean} [allowZero=true] * @returns {number} */ const validatePort = hideStackFrames( (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 ERR_SOCKET_BAD_PORT(name, port, allowZero); } return port | 0; }, ); /** * @callback validateAbortSignal * @param {*} signal * @param {string} name */ /** @type {validateAbortSignal} */ const validateAbortSignal = hideStackFrames((signal, name) => { if ( signal !== undefined && (signal === null || typeof signal !== "object" || !("aborted" in signal)) ) { throw new ERR_INVALID_ARG_TYPE(name, "AbortSignal", signal); } }); /** * @callback validateFunction * @param {*} value * @param {string} name * @returns {asserts value is Function} */ /** @type {validateFunction} */ const validateFunction = hideStackFrames((value, name) => { if (typeof value !== "function") throw new ERR_INVALID_ARG_TYPE(name, "Function", value); }); /** * @callback validatePlainFunction * @param {*} value * @param {string} name * @returns {asserts value is Function} */ /** @type {validatePlainFunction} */ const validatePlainFunction = hideStackFrames((value, name) => { if (typeof value !== "function" || isAsyncFunction(value)) throw new ERR_INVALID_ARG_TYPE(name, "Function", value); }); /** * @callback validateUndefined * @param {*} value * @param {string} name * @returns {asserts value is undefined} */ /** @type {validateUndefined} */ const validateUndefined = hideStackFrames((value, name) => { if (value !== undefined) throw new ERR_INVALID_ARG_TYPE(name, "undefined", value); }); /** * @template T * @param {T} value * @param {string} name * @param {T[]} union */ function validateUnion(value, name, union) { if (!Array.prototype.includes.call(union, value)) { throw new ERR_INVALID_ARG_TYPE( name, `('${Array.prototype.join.call(union, "|")}')`, value, ); } } /* The rules for the Link header field are described here: https://www.rfc-editor.org/rfc/rfc8288.html#section-3 This regex validates any string surrounded by angle brackets (not necessarily a valid URI reference) followed by zero or more link-params separated by semicolons. */ const linkValueRegExp = /^(?:<[^>]*>)(?:\s*;\s*[^;"\s]+(?:=(")?[^;"\s]*\1)?)*$/; /** * @param {any} value * @param {string} name */ const validateLinkHeaderFormat = hideStackFrames((value, name) => { if ( typeof value === "undefined" || !RegExp.prototype.exec.call(linkValueRegExp, value) ) { throw new ERR_INVALID_ARG_VALUE( name, value, 'must be an array or string of format "</styles.css>; rel=preload; as=style"', ); } }); const validateInternalField = hideStackFrames((object, fieldKey, className) => { if ( typeof object !== "object" || object === null || !Object.prototype.hasOwnProperty.call(object, fieldKey) ) { throw new ERR_INVALID_ARG_TYPE("this", className, object); } }); /** * @param {any} hints * @return {string} */ const validateLinkHeaderValue = hideStackFrames((hints) => { if (typeof hints === "string") { validateLinkHeaderFormat.withoutStackTrace(hints, "hints"); return hints; } else if (Array.isArray(hints)) { const hintsLength = hints.length; let result = ""; if (hintsLength === 0) { return result; } for (let i = 0; i < hintsLength; i++) { const link = hints[i]; validateLinkHeaderFormat.withoutStackTrace(link, "hints"); result += link; if (i !== hintsLength - 1) { result += ", "; } } return result; } throw new ERR_INVALID_ARG_VALUE( "hints", hints, 'must be an array or string of format "</styles.css>; rel=preload; as=style"', ); }); export { isInt32 }; export { isUint32 }; export { parseFileMode }; export { validateArray }; export { validateStringArray }; export { validateBooleanArray }; export { validateAbortSignalArray }; export { validateBoolean }; export { validateBuffer }; export { validateDictionary }; export { validateEncoding }; export { validateFunction }; export { validateInt32 }; export { validateInteger }; export { validateNumber }; export { validateObject }; export { kValidateObjectNone }; export { kValidateObjectAllowNullable }; export { kValidateObjectAllowArray }; export { kValidateObjectAllowFunction }; export { kValidateObjectAllowObjects }; export { kValidateObjectAllowObjectsAndNull }; export { validateOneOf }; export { validatePlainFunction }; export { validatePort }; export { validateSignalName }; export { validateString }; export { validateUint32 }; export { validateUndefined }; export { validateUnion }; export { validateAbortSignal }; export { validateLinkHeaderValue }; export { validateInternalField };