231 lines
7.2 KiB
JavaScript
231 lines
7.2 KiB
JavaScript
|
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,
|
||
|
};
|