mirror of
https://github.com/bitburner-official/bitburner-src.git
synced 2024-12-18 20:25:45 +01:00
fix ns.prompt typechecking
Introduced some new type assertion functions, initial step in larger type safety rework.
This commit is contained in:
parent
6c84d64e67
commit
ad0be471ce
@ -65,6 +65,9 @@ export const helpers = {
|
|||||||
failOnHacknetServer,
|
failOnHacknetServer,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/** Will probably remove the below function in favor of a different approach to object type assertion.
|
||||||
|
* This method cannot be used to handle optional properties. */
|
||||||
export function assertObjectType<T extends object>(
|
export function assertObjectType<T extends object>(
|
||||||
ctx: NetscriptContext,
|
ctx: NetscriptContext,
|
||||||
name: string,
|
name: string,
|
||||||
|
@ -61,14 +61,7 @@ import { NetscriptCorporation } from "./NetscriptFunctions/Corporation";
|
|||||||
import { NetscriptFormulas } from "./NetscriptFunctions/Formulas";
|
import { NetscriptFormulas } from "./NetscriptFunctions/Formulas";
|
||||||
import { NetscriptStockMarket } from "./NetscriptFunctions/StockMarket";
|
import { NetscriptStockMarket } from "./NetscriptFunctions/StockMarket";
|
||||||
import { NetscriptGrafting } from "./NetscriptFunctions/Grafting";
|
import { NetscriptGrafting } from "./NetscriptFunctions/Grafting";
|
||||||
import {
|
import { NS, RecentScript as IRecentScript, BasicHGWOptions, ProcessInfo } from "./ScriptEditor/NetscriptDefinitions";
|
||||||
NS,
|
|
||||||
RecentScript as IRecentScript,
|
|
||||||
BasicHGWOptions,
|
|
||||||
ProcessInfo,
|
|
||||||
MoneySource as IMoneySource,
|
|
||||||
MoneySources as IMoneySources,
|
|
||||||
} from "./ScriptEditor/NetscriptDefinitions";
|
|
||||||
import { NetscriptSingularity } from "./NetscriptFunctions/Singularity";
|
import { NetscriptSingularity } from "./NetscriptFunctions/Singularity";
|
||||||
|
|
||||||
import { dialogBoxCreate } from "./ui/React/DialogBox";
|
import { dialogBoxCreate } from "./ui/React/DialogBox";
|
||||||
@ -83,6 +76,7 @@ import { InternalAPI, wrapAPI } from "./Netscript/APIWrapper";
|
|||||||
import { INetscriptExtra } from "./NetscriptFunctions/Extra";
|
import { INetscriptExtra } from "./NetscriptFunctions/Extra";
|
||||||
import { ScriptDeath } from "./Netscript/ScriptDeath";
|
import { ScriptDeath } from "./Netscript/ScriptDeath";
|
||||||
import { getBitNodeMultipliers } from "./BitNode/BitNode";
|
import { getBitNodeMultipliers } from "./BitNode/BitNode";
|
||||||
|
import { assert, arrayAssert, stringAssert, objectAssert } from "./utils/helpers/typeAssertion";
|
||||||
|
|
||||||
// "Enums" as object
|
// "Enums" as object
|
||||||
export const enums = {
|
export const enums = {
|
||||||
@ -1759,60 +1753,81 @@ const base: InternalAPI<NS> = {
|
|||||||
throw new Error(`variant must be one of ${Object.values(ToastVariant).join(", ")}`);
|
throw new Error(`variant must be one of ${Object.values(ToastVariant).join(", ")}`);
|
||||||
SnackbarEvents.emit(message, variant as ToastVariant, duration);
|
SnackbarEvents.emit(message, variant as ToastVariant, duration);
|
||||||
},
|
},
|
||||||
prompt:
|
prompt: (ctx) => (_txt, _options) => {
|
||||||
(ctx) =>
|
const options: { type?: string; choices?: string[] } = {};
|
||||||
(_txt, options = {}) => {
|
_options ??= options;
|
||||||
const txt = helpers.string(ctx, "txt", _txt);
|
const txt = helpers.string(ctx, "txt", _txt);
|
||||||
const optionsValidator: { type?: string; options?: string[] } = {};
|
assert(_options, objectAssert, (type) =>
|
||||||
assertObjectType(ctx, "options", options, optionsValidator);
|
helpers.makeRuntimeErrorMsg(ctx, `Invalid type for options: ${type}, should be object.`, "TYPE"),
|
||||||
|
);
|
||||||
return new Promise(function (resolve) {
|
if (_options.type !== undefined) {
|
||||||
PromptEvent.emit({
|
assert(_options.type, stringAssert, (type) =>
|
||||||
txt: txt,
|
helpers.makeRuntimeErrorMsg(ctx, `Invalid type for options.type: ${type}, should be string.`, "TYPE"),
|
||||||
options,
|
);
|
||||||
resolve: resolve,
|
options.type = _options.type;
|
||||||
});
|
const validTypes = ["boolean", "text", "select"];
|
||||||
});
|
if (!["boolean", "text", "select"].includes(options.type)) {
|
||||||
},
|
throw helpers.makeRuntimeErrorMsg(
|
||||||
wget:
|
ctx,
|
||||||
(ctx) =>
|
`Invalid value for options.type: ${options.type}. Must be one of ${validTypes.join(", ")}.`,
|
||||||
async (_url, _target, _hostname = ctx.workerScript.hostname) => {
|
);
|
||||||
const url = helpers.string(ctx, "url", _url);
|
|
||||||
const target = helpers.string(ctx, "target", _target);
|
|
||||||
const hostname = helpers.string(ctx, "hostname", _hostname);
|
|
||||||
if (!isScriptFilename(target) && !target.endsWith(".txt")) {
|
|
||||||
helpers.log(ctx, () => `Invalid target file: '${target}'. Must be a script or text file.`);
|
|
||||||
return Promise.resolve(false);
|
|
||||||
}
|
}
|
||||||
const s = helpers.getServer(ctx, hostname);
|
if (options.type === "select") {
|
||||||
return new Promise(function (resolve) {
|
assert(_options.choices, arrayAssert, (type) =>
|
||||||
$.get(
|
helpers.makeRuntimeErrorMsg(
|
||||||
url,
|
ctx,
|
||||||
function (data) {
|
`Invalid type for options.choices: ${type}. If options.type is "select", options.choices must be an array.`,
|
||||||
let res;
|
"TYPE",
|
||||||
if (isScriptFilename(target)) {
|
),
|
||||||
res = s.writeToScriptFile(target, data);
|
);
|
||||||
} else {
|
options.choices = _options.choices.map((choice, i) => helpers.string(ctx, `options.choices[${i}]`, choice));
|
||||||
res = s.writeToTextFile(target, data);
|
}
|
||||||
}
|
}
|
||||||
if (!res.success) {
|
return new Promise(function (resolve) {
|
||||||
helpers.log(ctx, () => "Failed.");
|
PromptEvent.emit({
|
||||||
return resolve(false);
|
txt: txt,
|
||||||
}
|
options,
|
||||||
if (res.overwritten) {
|
resolve: resolve,
|
||||||
helpers.log(ctx, () => `Successfully retrieved content and overwrote '${target}' on '${hostname}'`);
|
|
||||||
return resolve(true);
|
|
||||||
}
|
|
||||||
helpers.log(ctx, () => `Successfully retrieved content to new file '${target}' on '${hostname}'`);
|
|
||||||
return resolve(true);
|
|
||||||
},
|
|
||||||
"text",
|
|
||||||
).fail(function (e) {
|
|
||||||
helpers.log(ctx, () => JSON.stringify(e));
|
|
||||||
return resolve(false);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
},
|
});
|
||||||
|
},
|
||||||
|
wget: (ctx) => async (_url, _target, _hostname) => {
|
||||||
|
const url = helpers.string(ctx, "url", _url);
|
||||||
|
const target = helpers.string(ctx, "target", _target);
|
||||||
|
const hostname = _hostname ? helpers.string(ctx, "hostname", _hostname) : ctx.workerScript.hostname;
|
||||||
|
if (!isScriptFilename(target) && !target.endsWith(".txt")) {
|
||||||
|
helpers.log(ctx, () => `Invalid target file: '${target}'. Must be a script or text file.`);
|
||||||
|
return Promise.resolve(false);
|
||||||
|
}
|
||||||
|
const s = helpers.getServer(ctx, hostname);
|
||||||
|
return new Promise(function (resolve) {
|
||||||
|
$.get(
|
||||||
|
url,
|
||||||
|
function (data) {
|
||||||
|
let res;
|
||||||
|
if (isScriptFilename(target)) {
|
||||||
|
res = s.writeToScriptFile(target, data);
|
||||||
|
} else {
|
||||||
|
res = s.writeToTextFile(target, data);
|
||||||
|
}
|
||||||
|
if (!res.success) {
|
||||||
|
helpers.log(ctx, () => "Failed.");
|
||||||
|
return resolve(false);
|
||||||
|
}
|
||||||
|
if (res.overwritten) {
|
||||||
|
helpers.log(ctx, () => `Successfully retrieved content and overwrote '${target}' on '${hostname}'`);
|
||||||
|
return resolve(true);
|
||||||
|
}
|
||||||
|
helpers.log(ctx, () => `Successfully retrieved content to new file '${target}' on '${hostname}'`);
|
||||||
|
return resolve(true);
|
||||||
|
},
|
||||||
|
"text",
|
||||||
|
).fail(function (e) {
|
||||||
|
helpers.log(ctx, () => JSON.stringify(e));
|
||||||
|
return resolve(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
getFavorToDonate: () => () => {
|
getFavorToDonate: () => () => {
|
||||||
return Math.floor(CONSTANTS.BaseFavorToDonate * BitNodeMultipliers.RepToDonateToFaction);
|
return Math.floor(CONSTANTS.BaseFavorToDonate * BitNodeMultipliers.RepToDonateToFaction);
|
||||||
},
|
},
|
||||||
|
2
src/ScriptEditor/NetscriptDefinitions.d.ts
vendored
2
src/ScriptEditor/NetscriptDefinitions.d.ts
vendored
@ -6613,7 +6613,7 @@ export interface NS {
|
|||||||
*/
|
*/
|
||||||
prompt(
|
prompt(
|
||||||
txt: string,
|
txt: string,
|
||||||
options?: { type?: "boolean" | "text" | "select" | undefined; choices?: string[] },
|
options?: { type?: "boolean" | "text" | "select"; choices?: string[] },
|
||||||
): Promise<boolean | string>;
|
): Promise<boolean | string>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
42
src/utils/helpers/typeAssertion.ts
Normal file
42
src/utils/helpers/typeAssertion.ts
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
// Various functions for asserting types.
|
||||||
|
|
||||||
|
/** 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<T>(
|
||||||
|
v: unknown,
|
||||||
|
assertFn: (v: unknown) => asserts v is T,
|
||||||
|
msgFn: (type: string) => string,
|
||||||
|
): asserts v is T {
|
||||||
|
try {
|
||||||
|
assertFn(v);
|
||||||
|
} catch (type: unknown) {
|
||||||
|
if (type !== "string") type = "unknown";
|
||||||
|
throw msgFn(type as string);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns the friendlyType of v. arrays are "array" and null is "null". */
|
||||||
|
export function getFriendlyType(v: unknown): string {
|
||||||
|
return v === null ? "null" : Array.isArray(v) ? "array" : typeof v;
|
||||||
|
}
|
||||||
|
|
||||||
|
//All assertion functions used here should return the friendlyType of the input.
|
||||||
|
|
||||||
|
/** For non-objects, and for array/null, throws the friendlyType of v. */
|
||||||
|
export function objectAssert(v: unknown): asserts v is Partial<Record<string, unknown>> {
|
||||||
|
const type = getFriendlyType(v);
|
||||||
|
if (type !== "object") throw type;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** For non-string, throws the friendlyType of v. */
|
||||||
|
export function stringAssert(v: unknown): asserts v is string {
|
||||||
|
const type = getFriendlyType(v);
|
||||||
|
if (type !== "string") throw type;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** For non-array, throws the friendlyType of v. */
|
||||||
|
export function arrayAssert(v: unknown): asserts v is unknown[] {
|
||||||
|
if (!Array.isArray(v)) throw getFriendlyType(v);
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user