From 9bf408221cb6f2622970578b32b5bbec44c8831c Mon Sep 17 00:00:00 2001 From: catloversg <152669316+catloversg@users.noreply.github.com> Date: Sat, 25 Jan 2025 02:06:39 +0700 Subject: [PATCH] CODEBASE: Merge TypeAssertion files (#1922) --- src/Bladeburner/Bladeburner.ts | 4 +- src/CodingContracts.ts | 4 +- src/Netscript/ErrorMessages.ts | 8 ++- src/Netscript/NetscriptHelpers.tsx | 6 +- src/Netscript/ScriptDeath.ts | 2 +- src/Netscript/TypeAssertion.ts | 12 +++- src/NetscriptFunctions.ts | 12 ++-- src/NetscriptFunctions/Bladeburner.ts | 6 +- .../Sleeve/Work/SleeveBladeburnerWork.ts | 4 +- .../Sleeve/Work/SleeveClassWork.ts | 4 +- src/SaveObject.ts | 4 +- src/Server/AllServers.ts | 4 +- src/Server/BaseServer.ts | 6 +- src/Settings/Settings.ts | 4 +- src/Types/Jsonable.ts | 8 +-- src/utils/EnumHelper.ts | 4 +- src/utils/JSONReviver.ts | 4 +- src/utils/TypeAssertion.ts | 70 +++++++++++++++++++ src/utils/helpers/typeAssertion.ts | 70 ------------------- 19 files changed, 125 insertions(+), 111 deletions(-) delete mode 100644 src/utils/helpers/typeAssertion.ts diff --git a/src/Bladeburner/Bladeburner.ts b/src/Bladeburner/Bladeburner.ts index 286602309..0f324f7d5 100644 --- a/src/Bladeburner/Bladeburner.ts +++ b/src/Bladeburner/Bladeburner.ts @@ -51,7 +51,7 @@ import { Sleeve } from "../PersonObjects/Sleeve/Sleeve"; import { autoCompleteTypeShorthand } from "./utils/terminalShorthands"; import { resolveTeamCasualties, type OperationTeam } from "./Actions/TeamCasualties"; import { shuffleArray } from "../Infiltration/ui/BribeGame"; -import { objectAssert } from "../utils/helpers/typeAssertion"; +import { assertObject } from "../utils/TypeAssertion"; import { throwIfReachable } from "../utils/helpers/throwIfReachable"; import { loadActionIdentifier } from "./utils/loadActionIdentifier"; @@ -1424,7 +1424,7 @@ export class Bladeburner implements OperationTeam { /** Initializes a Bladeburner object from a JSON save state. */ static fromJSON(value: IReviverValue): Bladeburner { - objectAssert(value.data); + assertObject(value.data); // operations and contracts are not loaded directly from the save, we load them in using a different method const contractsData = value.data.contracts; const operationsData = value.data.operations; diff --git a/src/CodingContracts.ts b/src/CodingContracts.ts index c0238be75..d48db9e7e 100644 --- a/src/CodingContracts.ts +++ b/src/CodingContracts.ts @@ -4,7 +4,7 @@ import { codingContractTypesMetadata } from "./data/codingcontracttypes"; import { Generic_fromJSON, Generic_toJSON, IReviverValue, constructorsForReviver } from "./utils/JSONReviver"; import { CodingContractEvent } from "./ui/React/CodingContractModal"; import { ContractFilePath, resolveContractFilePath } from "./Paths/ContractFilePath"; -import { objectAssert } from "./utils/helpers/typeAssertion"; +import { assertObject } from "./utils/TypeAssertion"; /* Contract Types */ export const CodingContractTypes = Object.fromEntries(codingContractTypesMetadata.map((x) => [x.name, x])); @@ -128,7 +128,7 @@ export class CodingContract { /** Initializes a CodingContract from a JSON save state. */ static fromJSON(value: IReviverValue): CodingContract { - objectAssert(value.data); + assertObject(value.data); // In previous versions, there was a data field instead of a state field. if ("data" in value.data) { value.data.state = value.data.data; diff --git a/src/Netscript/ErrorMessages.ts b/src/Netscript/ErrorMessages.ts index 8064140f3..fc5fe31ea 100644 --- a/src/Netscript/ErrorMessages.ts +++ b/src/Netscript/ErrorMessages.ts @@ -17,7 +17,13 @@ export function basicErrorMessage(ws: WorkerScript | ScriptDeath, msg: string, t return `${type} ERROR\n${ws.name}@${ws.hostname} (PID - ${ws.pid})\n\n${msg}`; } -/** Creates an error message string with a stack trace. */ +/** + * Creates an error message string with a stack trace. + * + * When the player provides invalid input, we try to provide a stack trace that points to the player's invalid caller, + * but we don't have an error instance with a stack trace. In order to get that stack trace, we create a new error + * instance, then remove "unrelated" traces (code in our codebase) and leave only traces of the player's code. + */ export function errorMessage(ctx: NetscriptContext, msg: string, type = "RUNTIME"): string { const errstack = new Error().stack; if (errstack === undefined) throw new Error("how did we not throw an error?"); diff --git a/src/Netscript/NetscriptHelpers.tsx b/src/Netscript/NetscriptHelpers.tsx index 466dc7719..1d7b6f098 100644 --- a/src/Netscript/NetscriptHelpers.tsx +++ b/src/Netscript/NetscriptHelpers.tsx @@ -56,7 +56,7 @@ import { hasScriptExtension, ScriptFilePath } from "../Paths/ScriptFilePath"; import { CustomBoundary } from "../ui/Components/CustomBoundary"; import { ServerConstants } from "../Server/data/Constants"; import { basicErrorMessage, errorMessage, log } from "./ErrorMessages"; -import { assertString, debugType } from "./TypeAssertion"; +import { assertStringWithNSContext, debugType } from "./TypeAssertion"; import { canAccessBitNodeFeature, getDefaultBitNodeOptions, @@ -124,7 +124,7 @@ export interface CompleteHGWOptions { /** Convert a provided value v for argument argName to string. If it wasn't originally a string or number, throw. */ function string(ctx: NetscriptContext, argName: string, v: unknown): string { if (typeof v === "number") v = v + ""; // cast to string; - assertString(ctx, argName, v); + assertStringWithNSContext(ctx, argName, v); return v; } @@ -642,7 +642,7 @@ function gangTask(ctx: NetscriptContext, t: unknown): GangMemberTask { } export function filePath(ctx: NetscriptContext, argName: string, filename: unknown): FilePath { - assertString(ctx, argName, filename); + assertStringWithNSContext(ctx, argName, filename); const path = resolveFilePath(filename, ctx.workerScript.name); if (path) return path; throw errorMessage(ctx, `Invalid ${argName}, was not a valid path: ${filename}`); diff --git a/src/Netscript/ScriptDeath.ts b/src/Netscript/ScriptDeath.ts index 576eb560d..55566c43e 100644 --- a/src/Netscript/ScriptDeath.ts +++ b/src/Netscript/ScriptDeath.ts @@ -1,4 +1,4 @@ -import { WorkerScript } from "./WorkerScript"; +import type { WorkerScript } from "./WorkerScript"; /** * Script death marker. diff --git a/src/Netscript/TypeAssertion.ts b/src/Netscript/TypeAssertion.ts index fe6709e75..24d0ba84f 100644 --- a/src/Netscript/TypeAssertion.ts +++ b/src/Netscript/TypeAssertion.ts @@ -23,10 +23,18 @@ export const debugType = (v: unknown): string => { return `Is of type '${typeof v}', value: ${userFriendlyString(v)}`; }; -export function assertString(ctx: NetscriptContext, argName: string, v: unknown): asserts v is string { +/** + * This function should be used to assert strings provided by the player. It uses a specialized utility function that + * provides a stack trace pointing to the player's invalid caller. + */ +export function assertStringWithNSContext(ctx: NetscriptContext, argName: string, v: unknown): asserts v is string { if (typeof v !== "string") throw errorMessage(ctx, `${argName} expected to be a string. ${debugType(v)}`, "TYPE"); } -export function assertFunction(ctx: NetscriptContext, argName: string, v: unknown): asserts v is () => void { +export function assertFunctionWithNSContext( + ctx: NetscriptContext, + argName: string, + v: unknown, +): asserts v is () => void { if (typeof v !== "function") throw errorMessage(ctx, `${argName} expected to be a function ${debugType(v)}`, "TYPE"); } diff --git a/src/NetscriptFunctions.ts b/src/NetscriptFunctions.ts index e98996517..1c17e76d0 100644 --- a/src/NetscriptFunctions.ts +++ b/src/NetscriptFunctions.ts @@ -94,7 +94,7 @@ import { InternalAPI, setRemovedFunctions, NSProxy } from "./Netscript/APIWrappe import { INetscriptExtra } from "./NetscriptFunctions/Extra"; import { ScriptDeath } from "./Netscript/ScriptDeath"; import { getBitNodeMultipliers } from "./BitNode/BitNode"; -import { assert, arrayAssert, stringAssert, objectAssert } from "./utils/helpers/typeAssertion"; +import { assert, assertArray, assertString, assertObject } from "./utils/TypeAssertion"; import { escapeRegExp } from "lodash"; import numeral from "numeral"; import { clearPort, peekPort, portHandle, readPort, tryWritePort, writePort, nextPortWrite } from "./NetscriptPort"; @@ -107,7 +107,7 @@ import { getRamCost } from "./Netscript/RamCostGenerator"; import { getEnumHelper } from "./utils/EnumHelper"; import { setDeprecatedProperties, deprecationWarning } from "./utils/DeprecationHelper"; import { ServerConstants } from "./Server/data/Constants"; -import { assertFunction } from "./Netscript/TypeAssertion"; +import { assertFunctionWithNSContext } from "./Netscript/TypeAssertion"; import { Router } from "./ui/GameRoot"; import { Page } from "./ui/Router"; import { canAccessBitNodeFeature, validBitNodes } from "./BitNode/BitNodeUtils"; @@ -1694,11 +1694,11 @@ export const ns: InternalAPI = { const options: { type?: string; choices?: string[] } = {}; _options ??= options; const txt = helpers.string(ctx, "txt", _txt); - assert(_options, objectAssert, (type) => + assert(_options, assertObject, (type) => helpers.errorMessage(ctx, `Invalid type for options: ${type}. Should be object.`, "TYPE"), ); if (_options.type !== undefined) { - assert(_options.type, stringAssert, (type) => + assert(_options.type, assertString, (type) => helpers.errorMessage(ctx, `Invalid type for options.type: ${type}. Should be string.`, "TYPE"), ); options.type = _options.type; @@ -1710,7 +1710,7 @@ export const ns: InternalAPI = { ); } if (options.type === "select") { - assert(_options.choices, arrayAssert, (type) => + assert(_options.choices, assertArray, (type) => helpers.errorMessage( ctx, `Invalid type for options.choices: ${type}. If options.type is "select", options.choices must be an array.`, @@ -1811,7 +1811,7 @@ export const ns: InternalAPI = { }), atExit: (ctx) => (callback, _id) => { const id = _id ? helpers.string(ctx, "id", _id) : "default"; - assertFunction(ctx, "callback", callback); + assertFunctionWithNSContext(ctx, "callback", callback); ctx.workerScript.atExit.set(id, callback); }, mv: (ctx) => (_host, _source, _destination) => { diff --git a/src/NetscriptFunctions/Bladeburner.ts b/src/NetscriptFunctions/Bladeburner.ts index 8836aef26..71d7694dc 100644 --- a/src/NetscriptFunctions/Bladeburner.ts +++ b/src/NetscriptFunctions/Bladeburner.ts @@ -15,7 +15,7 @@ import { currentNodeMults } from "../BitNode/BitNodeMultipliers"; import { helpers } from "../Netscript/NetscriptHelpers"; import { getEnumHelper } from "../utils/EnumHelper"; import { Skills } from "../Bladeburner/data/Skills"; -import { assertString } from "../Netscript/TypeAssertion"; +import { assertStringWithNSContext } from "../Netscript/TypeAssertion"; import { BlackOperations, blackOpsArray } from "../Bladeburner/data/BlackOperations"; import { checkSleeveAPIAccess, checkSleeveNumber } from "../NetscriptFunctions/Sleeve"; import { canAccessBitNodeFeature } from "../BitNode/BitNodeUtils"; @@ -37,8 +37,8 @@ export function NetscriptBladeburner(): InternalAPI { }; function getAction(ctx: NetscriptContext, type: unknown, name: unknown): Action { const bladeburner = Player.bladeburner; - assertString(ctx, "type", type); - assertString(ctx, "name", name); + assertStringWithNSContext(ctx, "type", type); + assertStringWithNSContext(ctx, "name", name); if (bladeburner === null) throw new Error("Must have joined bladeburner"); const action = bladeburner.getActionFromTypeAndName(type, name); if (!action) throw helpers.errorMessage(ctx, `Invalid action type='${type}', name='${name}'`); diff --git a/src/PersonObjects/Sleeve/Work/SleeveBladeburnerWork.ts b/src/PersonObjects/Sleeve/Work/SleeveBladeburnerWork.ts index ab66a9aca..02d74c9bf 100644 --- a/src/PersonObjects/Sleeve/Work/SleeveBladeburnerWork.ts +++ b/src/PersonObjects/Sleeve/Work/SleeveBladeburnerWork.ts @@ -10,7 +10,7 @@ import { scaleWorkStats } from "../../../Work/WorkStats"; import { getKeyList } from "../../../utils/helpers/getKeyList"; import { loadActionIdentifier } from "../../../Bladeburner/utils/loadActionIdentifier"; import { invalidWork } from "../../../Work/InvalidWork"; -import { objectAssert } from "../../../utils/helpers/typeAssertion"; +import { assertObject } from "../../../utils/TypeAssertion"; interface SleeveBladeburnerWorkParams { actionId: ActionIdentifier & { type: BladeburnerActionType.General | BladeburnerActionType.Contract }; @@ -99,7 +99,7 @@ export class SleeveBladeburnerWork extends SleeveWorkClass { /** Initializes a BladeburnerWork object from a JSON save state. */ static fromJSON(value: IReviverValue): SleeveBladeburnerWork { - objectAssert(value.data); + assertObject(value.data); let actionId = loadActionIdentifier(value.data?.actionId); if (!actionId) { /** diff --git a/src/PersonObjects/Sleeve/Work/SleeveClassWork.ts b/src/PersonObjects/Sleeve/Work/SleeveClassWork.ts index 896a9a6dd..8923bee96 100644 --- a/src/PersonObjects/Sleeve/Work/SleeveClassWork.ts +++ b/src/PersonObjects/Sleeve/Work/SleeveClassWork.ts @@ -7,7 +7,7 @@ import { Sleeve } from "../Sleeve"; import { scaleWorkStats, WorkStats } from "../../../Work/WorkStats"; import { Locations } from "../../../Locations/Locations"; import { isMember } from "../../../utils/EnumHelper"; -import { objectAssert } from "../../../utils/helpers/typeAssertion"; +import { assertObject } from "../../../utils/TypeAssertion"; export const isSleeveClassWork = (w: SleeveWorkClass | null): w is SleeveClassWork => w !== null && w.type === SleeveWorkType.CLASS; @@ -55,7 +55,7 @@ export class SleeveClassWork extends SleeveWorkClass { /** Initializes a ClassWork object from a JSON save state. */ static fromJSON(value: IReviverValue): SleeveClassWork { - objectAssert(value.data); + assertObject(value.data); if (typeof value.data.classType !== "string" || !(value.data.classType in Classes)) { value.data.classType = "Computer Science"; } diff --git a/src/SaveObject.ts b/src/SaveObject.ts index 8096cba2a..29b320829 100644 --- a/src/SaveObject.ts +++ b/src/SaveObject.ts @@ -26,7 +26,7 @@ import { SaveDataError, canUseBinaryFormat, decodeSaveData, encodeJsonSaveString import { isBinaryFormat } from "../electron/saveDataBinaryFormat"; import { downloadContentAsFile } from "./utils/FileUtils"; import { handleGetSaveDataInfoError } from "./utils/ErrorHandler"; -import { isObject, objectAssert } from "./utils/helpers/typeAssertion"; +import { isObject, assertObject } from "./utils/TypeAssertion"; import { evaluateVersionCompatibility } from "./utils/SaveDataMigrationUtils"; import { Reviver } from "./utils/GenericReviver"; @@ -99,7 +99,7 @@ export type BitburnerSaveObjectType = { * If saveObject has these properties, we check if their values are strings. */ function assertBitburnerSaveObjectType(saveObject: unknown): asserts saveObject is BitburnerSaveObjectType { - objectAssert(saveObject); + assertObject(saveObject); const mandatoryKeysOfSaveObj = [ "PlayerSave", diff --git a/src/Server/AllServers.ts b/src/Server/AllServers.ts index 4b4fbd110..4e2d40356 100644 --- a/src/Server/AllServers.ts +++ b/src/Server/AllServers.ts @@ -13,7 +13,7 @@ import { currentNodeMults } from "../BitNode/BitNodeMultipliers"; import { IPAddress, isIPAddress } from "../Types/strings"; import "../Script/RunningScript"; // For reviver side-effect -import { objectAssert } from "../utils/helpers/typeAssertion"; +import { assertObject } from "../utils/TypeAssertion"; /** * Map of all Servers that exist in the game @@ -218,7 +218,7 @@ export function prestigeAllServers(): void { export function loadAllServers(saveString: string): void { const allServersData: unknown = JSON.parse(saveString, Reviver); - objectAssert(allServersData); + assertObject(allServersData); for (const [serverName, server] of Object.entries(allServersData)) { if (!(server instanceof Server) && !(server instanceof HacknetServer)) { throw new Error(`Server ${serverName} is not an instance of Server or HacknetServer.`); diff --git a/src/Server/BaseServer.ts b/src/Server/BaseServer.ts index de79d70f0..aacf37ce2 100644 --- a/src/Server/BaseServer.ts +++ b/src/Server/BaseServer.ts @@ -24,7 +24,7 @@ import lodash from "lodash"; import { Settings } from "../Settings/Settings"; import type { ScriptKey } from "../utils/helpers/scriptKey"; -import { objectAssert } from "../utils/helpers/typeAssertion"; +import { assertObject } from "../utils/TypeAssertion"; import { clampNumber } from "../utils/helpers/clampNumber"; interface IConstructorParams { @@ -295,7 +295,7 @@ export abstract class BaseServer implements IServer { // RunningScripts are stored as a simple array, both for backward compatibility, // compactness, and ease of filtering them here. const result = Generic_toJSON(ctorName, this, keys); - objectAssert(result.data); + assertObject(result.data); if (Settings.ExcludeRunningScriptsFromSave) { result.data.runningScripts = []; return result; @@ -316,7 +316,7 @@ export abstract class BaseServer implements IServer { // Initializes a Server Object from a JSON save state // Called by subclasses, not Reviver. static fromJSONBase(value: IReviverValue, ctor: new () => T, keys: readonly (keyof T)[]): T { - objectAssert(value.data); + assertObject(value.data); const server = Generic_fromJSON(ctor, value.data, keys); if (value.data.runningScripts != null && Array.isArray(value.data.runningScripts)) { server.savedScripts = value.data.runningScripts; diff --git a/src/Settings/Settings.ts b/src/Settings/Settings.ts index 3ff4dc52b..655f55311 100644 --- a/src/Settings/Settings.ts +++ b/src/Settings/Settings.ts @@ -3,7 +3,7 @@ import { defaultTheme } from "../Themes/Themes"; import { defaultStyles } from "../Themes/Styles"; import { CursorStyle, CursorBlinking, WordWrapOptions } from "../ScriptEditor/ui/Options"; import { defaultMonacoTheme } from "../ScriptEditor/ui/themes"; -import { objectAssert } from "../utils/helpers/typeAssertion"; +import { assertObject } from "../utils/TypeAssertion"; import { Result } from "../types"; import { assertAndSanitizeEditorTheme, @@ -186,7 +186,7 @@ export const Settings = { load(saveString: string) { const save: unknown = JSON.parse(saveString); - objectAssert(save); + assertObject(save); save.overview && Object.assign(Settings.overview, save.overview); try { // Sanitize theme data. Invalid theme data may crash the game or make it stuck in the loading page. diff --git a/src/Types/Jsonable.ts b/src/Types/Jsonable.ts index db0b311fe..4793f4d9c 100644 --- a/src/Types/Jsonable.ts +++ b/src/Types/Jsonable.ts @@ -1,4 +1,4 @@ -import { arrayAssert } from "../utils/helpers/typeAssertion"; +import { assertArray } from "../utils/TypeAssertion"; import type { IReviverValue } from "../utils/JSONReviver"; // Versions of js builtin classes that can be converted to and from JSON for use in save files @@ -7,7 +7,7 @@ export class JSONSet extends Set { return { ctor: "JSONSet", data: Array.from(this) }; } static fromJSON(value: IReviverValue): JSONSet { - arrayAssert(value.data); + assertArray(value.data); return new JSONSet(value.data); } } @@ -18,9 +18,9 @@ export class JSONMap extends Map { } static fromJSON(value: IReviverValue): JSONMap { - arrayAssert(value.data); + assertArray(value.data); for (const item of value.data) { - arrayAssert(item); + assertArray(item); if (item.length !== 2) { console.error("Invalid data passed to JSONMap.fromJSON(). Value:", value); throw new Error(`An item is not an array with exactly 2 items. Its length is ${item.length}.`); diff --git a/src/utils/EnumHelper.ts b/src/utils/EnumHelper.ts index 723b18413..e77d3a8da 100644 --- a/src/utils/EnumHelper.ts +++ b/src/utils/EnumHelper.ts @@ -2,7 +2,7 @@ import type { Member } from "../types"; import type { NetscriptContext } from "../Netscript/APIWrapper"; import * as allEnums from "../Enums"; -import { assertString } from "../Netscript/TypeAssertion"; +import { assertStringWithNSContext } from "../Netscript/TypeAssertion"; import { errorMessage } from "../Netscript/ErrorMessages"; import { getRandomIntInclusive } from "./helpers/getRandomIntInclusive"; import { getRecordValues } from "../Types/Record"; @@ -43,7 +43,7 @@ class EnumHelper & st if (match) return match; // No match found, create error message - assertString(ctx, argName, toValidate); + assertStringWithNSContext(ctx, argName, toValidate); let allowableValues = `Allowable values: ${this.valueArray.map((val) => `"${val}"`).join(", ")}`; // Don't display all possibilities for large enums if (this.valueArray.length > 10) { diff --git a/src/utils/JSONReviver.ts b/src/utils/JSONReviver.ts index 9c015c3b4..ad39e6adc 100644 --- a/src/utils/JSONReviver.ts +++ b/src/utils/JSONReviver.ts @@ -1,7 +1,7 @@ /* Generic Reviver, toJSON, and fromJSON functions used for saving and loading objects */ import { ObjectValidator } from "./Validator"; import { JSONMap, JSONSet } from "../Types/Jsonable"; -import { objectAssert } from "./helpers/typeAssertion"; +import { assertObject } from "./TypeAssertion"; type JsonableClass = (new () => { toJSON: () => IReviverValue }) & { fromJSON: (value: IReviverValue) => unknown; @@ -58,7 +58,7 @@ export function Generic_fromJSON>( data: unknown, keys?: readonly (keyof T)[], ): T { - objectAssert(data); + assertObject(data); const obj = new ctor(); // If keys were provided, just load the provided keys (if they are in the data) if (keys) { diff --git a/src/utils/TypeAssertion.ts b/src/utils/TypeAssertion.ts index 1937e4e9a..19c9e5f5f 100644 --- a/src/utils/TypeAssertion.ts +++ b/src/utils/TypeAssertion.ts @@ -1,4 +1,74 @@ import type { Unknownify } from "../types"; + // This function is empty because Unknownify is a typesafe assertion on any object with no runtime checks needed. // eslint-disable-next-line @typescript-eslint/no-empty-function export function assertLoadingType(val: object): asserts val is Unknownify {} + +export class TypeAssertionError extends Error { + friendlyType: string; + + constructor(message: string, friendlyType: string, options?: ErrorOptions) { + super(message, options); + this.name = this.constructor.name; + this.friendlyType = friendlyType; + } +} + +/** Function for providing custom error message to throw for a type assertion. + * @param v: Value to assert type of + * @param assertFn: Typechecking function to use for asserting type of v. + * @param msgFn: Function to use to generate an error message if an error is produced. */ +export function assert( + v: unknown, + assertFn: (v: unknown) => asserts v is T, + msgFn: (type: string) => string, +): asserts v is T { + try { + assertFn(v); + } catch (e) { + if (e instanceof TypeAssertionError) { + throw msgFn(e.friendlyType); + } + const type = typeof e === "string" ? e : "unknown"; + throw msgFn(type); + } +} + +/** Returns the friendlyType of v. arrays are "array" and null is "null". */ +function getFriendlyType(v: unknown): string { + return v === null ? "null" : Array.isArray(v) ? "array" : typeof v; +} + +export function isObject(v: unknown): v is Record { + return getFriendlyType(v) === "object"; +} + +/** For non-objects, and for array/null, throws an error with the friendlyType of v. */ +export function assertObject(v: unknown): asserts v is Record { + const type = getFriendlyType(v); + if (type !== "object") { + console.error("The value is not an object. Value:", v); + throw new TypeAssertionError( + `The value is not an object. Its type is ${type}. Its string value is ${String(v)}.`, + type, + ); + } +} + +/** For non-string, throws an error with the friendlyType of v. */ +export function assertString(v: unknown): asserts v is string { + const type = getFriendlyType(v); + if (type !== "string") { + console.error("The value is not a string. Value:", v); + throw new TypeAssertionError(`The value is not an string. Its type is ${type}.`, type); + } +} + +/** For non-array, throws an error with the friendlyType of v. */ +export function assertArray(v: unknown): asserts v is unknown[] { + if (!Array.isArray(v)) { + console.error("The value is not an array. Value:", v); + const type = getFriendlyType(v); + throw new TypeAssertionError(`The value is not an array. Its type is ${type}.`, type); + } +} diff --git a/src/utils/helpers/typeAssertion.ts b/src/utils/helpers/typeAssertion.ts deleted file mode 100644 index 80cc19b51..000000000 --- a/src/utils/helpers/typeAssertion.ts +++ /dev/null @@ -1,70 +0,0 @@ -// Various functions for asserting types. - -export class TypeAssertionError extends Error { - friendlyType: string; - - constructor(message: string, friendlyType: string, options?: ErrorOptions) { - super(message, options); - this.name = this.constructor.name; - this.friendlyType = friendlyType; - } -} - -/** Function for providing custom error message to throw for a type assertion. - * @param v: Value to assert type of - * @param assertFn: Typechecking function to use for asserting type of v. - * @param msgFn: Function to use to generate an error message if an error is produced. */ -export function assert( - v: unknown, - assertFn: (v: unknown) => asserts v is T, - msgFn: (type: string) => string, -): asserts v is T { - try { - assertFn(v); - } catch (e) { - if (e instanceof TypeAssertionError) { - throw msgFn(e.friendlyType); - } - const type = typeof e === "string" ? e : "unknown"; - throw msgFn(type); - } -} - -/** Returns the friendlyType of v. arrays are "array" and null is "null". */ -function getFriendlyType(v: unknown): string { - return v === null ? "null" : Array.isArray(v) ? "array" : typeof v; -} - -export function isObject(v: unknown): v is Record { - return getFriendlyType(v) === "object"; -} - -/** For non-objects, and for array/null, throws an error with the friendlyType of v. */ -export function objectAssert(v: unknown): asserts v is Record { - const type = getFriendlyType(v); - if (type !== "object") { - console.error("The value is not an object. Value:", v); - throw new TypeAssertionError( - `The value is not an object. Its type is ${type}. Its string value is ${String(v)}.`, - type, - ); - } -} - -/** For non-string, throws an error with the friendlyType of v. */ -export function stringAssert(v: unknown): asserts v is string { - const type = getFriendlyType(v); - if (type !== "string") { - console.error("The value is not a string. Value:", v); - throw new TypeAssertionError(`The value is not an string. Its type is ${type}.`, type); - } -} - -/** For non-array, throws an error with the friendlyType of v. */ -export function arrayAssert(v: unknown): asserts v is unknown[] { - if (!Array.isArray(v)) { - console.error("The value is not an array. Value:", v); - const type = getFriendlyType(v); - throw new TypeAssertionError(`The value is not an array. Its type is ${type}.`, type); - } -}