export const parseBoolean = (value) => { switch (value) { case "true": return true; case "false": return false; default: throw new Error(`Unable to parse boolean value "${value}"`); } }; export const expectBoolean = (value) => { if (value === null || value === undefined) { return undefined; } if (typeof value === "number") { if (value === 0 || value === 1) { logger.warn(stackTraceWarning(`Expected boolean, got ${typeof value}: ${value}`)); } if (value === 0) { return false; } if (value === 1) { return true; } } if (typeof value === "string") { const lower = value.toLowerCase(); if (lower === "false" || lower === "true") { logger.warn(stackTraceWarning(`Expected boolean, got ${typeof value}: ${value}`)); } if (lower === "false") { return false; } if (lower === "true") { return true; } } if (typeof value === "boolean") { return value; } throw new TypeError(`Expected boolean, got ${typeof value}: ${value}`); }; export const expectNumber = (value) => { if (value === null || value === undefined) { return undefined; } if (typeof value === "string") { const parsed = parseFloat(value); if (!Number.isNaN(parsed)) { if (String(parsed) !== String(value)) { logger.warn(stackTraceWarning(`Expected number but observed string: ${value}`)); } return parsed; } } if (typeof value === "number") { return value; } throw new TypeError(`Expected number, got ${typeof value}: ${value}`); }; const MAX_FLOAT = Math.ceil(2 ** 127 * (2 - 2 ** -23)); export const expectFloat32 = (value) => { const expected = expectNumber(value); if (expected !== undefined && !Number.isNaN(expected) && expected !== Infinity && expected !== -Infinity) { if (Math.abs(expected) > MAX_FLOAT) { throw new TypeError(`Expected 32-bit float, got ${value}`); } } return expected; }; export const expectLong = (value) => { if (value === null || value === undefined) { return undefined; } if (Number.isInteger(value) && !Number.isNaN(value)) { return value; } throw new TypeError(`Expected integer, got ${typeof value}: ${value}`); }; export const expectInt = expectLong; export const expectInt32 = (value) => expectSizedInt(value, 32); export const expectShort = (value) => expectSizedInt(value, 16); export const expectByte = (value) => expectSizedInt(value, 8); const expectSizedInt = (value, size) => { const expected = expectLong(value); if (expected !== undefined && castInt(expected, size) !== expected) { throw new TypeError(`Expected ${size}-bit integer, got ${value}`); } return expected; }; const castInt = (value, size) => { switch (size) { case 32: return Int32Array.of(value)[0]; case 16: return Int16Array.of(value)[0]; case 8: return Int8Array.of(value)[0]; } }; export const expectNonNull = (value, location) => { if (value === null || value === undefined) { if (location) { throw new TypeError(`Expected a non-null value for ${location}`); } throw new TypeError("Expected a non-null value"); } return value; }; export const expectObject = (value) => { if (value === null || value === undefined) { return undefined; } if (typeof value === "object" && !Array.isArray(value)) { return value; } const receivedType = Array.isArray(value) ? "array" : typeof value; throw new TypeError(`Expected object, got ${receivedType}: ${value}`); }; export const expectString = (value) => { if (value === null || value === undefined) { return undefined; } if (typeof value === "string") { return value; } if (["boolean", "number", "bigint"].includes(typeof value)) { logger.warn(stackTraceWarning(`Expected string, got ${typeof value}: ${value}`)); return String(value); } throw new TypeError(`Expected string, got ${typeof value}: ${value}`); }; export const expectUnion = (value) => { if (value === null || value === undefined) { return undefined; } const asObject = expectObject(value); const setKeys = Object.entries(asObject) .filter(([, v]) => v != null) .map(([k]) => k); if (setKeys.length === 0) { throw new TypeError(`Unions must have exactly one non-null member. None were found.`); } if (setKeys.length > 1) { throw new TypeError(`Unions must have exactly one non-null member. Keys ${setKeys} were not null.`); } return asObject; }; export const strictParseDouble = (value) => { if (typeof value == "string") { return expectNumber(parseNumber(value)); } return expectNumber(value); }; export const strictParseFloat = strictParseDouble; export const strictParseFloat32 = (value) => { if (typeof value == "string") { return expectFloat32(parseNumber(value)); } return expectFloat32(value); }; const NUMBER_REGEX = /(-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?)|(-?Infinity)|(NaN)/g; const parseNumber = (value) => { const matches = value.match(NUMBER_REGEX); if (matches === null || matches[0].length !== value.length) { throw new TypeError(`Expected real number, got implicit NaN`); } return parseFloat(value); }; export const limitedParseDouble = (value) => { if (typeof value == "string") { return parseFloatString(value); } return expectNumber(value); }; export const handleFloat = limitedParseDouble; export const limitedParseFloat = limitedParseDouble; export const limitedParseFloat32 = (value) => { if (typeof value == "string") { return parseFloatString(value); } return expectFloat32(value); }; const parseFloatString = (value) => { switch (value) { case "NaN": return NaN; case "Infinity": return Infinity; case "-Infinity": return -Infinity; default: throw new Error(`Unable to parse float value: ${value}`); } }; export const strictParseLong = (value) => { if (typeof value === "string") { return expectLong(parseNumber(value)); } return expectLong(value); }; export const strictParseInt = strictParseLong; export const strictParseInt32 = (value) => { if (typeof value === "string") { return expectInt32(parseNumber(value)); } return expectInt32(value); }; export const strictParseShort = (value) => { if (typeof value === "string") { return expectShort(parseNumber(value)); } return expectShort(value); }; export const strictParseByte = (value) => { if (typeof value === "string") { return expectByte(parseNumber(value)); } return expectByte(value); }; const stackTraceWarning = (message) => { return String(new TypeError(message).stack || message) .split("\n") .slice(0, 5) .filter((s) => !s.includes("stackTraceWarning")) .join("\n"); }; export const logger = { warn: console.warn, };