From d9064b608f8969db4e1cdb2d5070066ea1d338a1 Mon Sep 17 00:00:00 2001 From: TheMas3212 Date: Mon, 10 Jan 2022 05:50:11 +1100 Subject: [PATCH 1/2] PoC for save validation on load via hooking the Reviver function and static property validationData on classes PoC/example implemented with HacknetNode: validates cores is a number in the range between 1 and HacknetNodeConstants.MaxCores validates level is a number in range between 1 and HacknetNodeConstants.MaxLevel validates ram is a number in range between 1 and HacknetNodeConstants.MaxRam validates onlineTimeSeconds in non negative number validates totalMoneyGenerated is a non negative number --- src/Hacknet/HacknetNode.ts | 28 ++++++++++++++++++++++++++++ src/utils/JSONReviver.ts | 8 +++++++- src/utils/Validator.ts | 31 +++++++++++++++++++++++++++++++ 3 files changed, 66 insertions(+), 1 deletion(-) create mode 100644 src/utils/Validator.ts diff --git a/src/Hacknet/HacknetNode.ts b/src/Hacknet/HacknetNode.ts index 4982b606b..9d79f334d 100644 --- a/src/Hacknet/HacknetNode.ts +++ b/src/Hacknet/HacknetNode.ts @@ -18,8 +18,36 @@ import { HacknetNodeConstants } from "./data/Constants"; import { dialogBoxCreate } from "../ui/React/DialogBox"; import { Generic_fromJSON, Generic_toJSON, Reviver } from "../utils/JSONReviver"; +import { ObjectValidator } from "src/utils/Validator"; export class HacknetNode implements IHacknetNode { + + static validationData: ObjectValidator = { + cores: { + default: 1, + min: 1, + max: HacknetNodeConstants.MaxCores + }, + level: { + default: 1, + min: 1, + max: HacknetNodeConstants.MaxLevel + }, + ram: { + default: 1, + min: 1, + max: HacknetNodeConstants.MaxRam + }, + onlineTimeSeconds: { + default: 0, + min: 0 + }, + totalMoneyGenerated: { + default: 0, + min: 0 + } + } + // Node's number of cores cores = 1; diff --git a/src/utils/JSONReviver.ts b/src/utils/JSONReviver.ts index eeb832cec..5b7447d09 100644 --- a/src/utils/JSONReviver.ts +++ b/src/utils/JSONReviver.ts @@ -1,5 +1,7 @@ /* Generic Reviver, toJSON, and fromJSON functions used for saving and loading objects */ +import { validateObject } from "./Validator"; + interface IReviverValue { ctor: string; data: any; @@ -26,7 +28,11 @@ export function Reviver(key: string, value: IReviverValue | null): any { const ctor = Reviver.constructors[value.ctor]; if (typeof ctor === "function" && typeof ctor.fromJSON === "function") { - return ctor.fromJSON(value); + const obj = ctor.fromJSON(value); + if (ctor.validationData !== undefined) { + validateObject(obj, ctor.validationData); + } + return obj; } } return value; diff --git a/src/utils/Validator.ts b/src/utils/Validator.ts new file mode 100644 index 000000000..a97b5b039 --- /dev/null +++ b/src/utils/Validator.ts @@ -0,0 +1,31 @@ +export type ObjectValidator = { + [key in keyof T]?: ParameterValidator; +} + +interface ParameterValidator { + default?: any; + min?: number; + max?: number; + func?: (obj: T, validator: ObjectValidator, key: U) => void; +} + +export function validateObject, U extends keyof T>(obj: T, validator: ObjectValidator): void { + for (const key of Object.keys(validator) as U[]) { + const paramValidator = validator[key]; + if (paramValidator !== undefined) { + if (paramValidator.func !== undefined) { + paramValidator.func(obj, validator, key); + } else { + if ((typeof obj[key]) !== (typeof paramValidator.default)) { + obj[key] = paramValidator.default + } + if (typeof obj[key] === 'number' && paramValidator.min !== undefined) { + if (obj[key] < paramValidator.min) obj[key] = paramValidator.min as T[U]; + } + if (typeof obj[key] === 'number' && paramValidator.max !== undefined) { + if (obj[key] > paramValidator.max) obj[key] = paramValidator.max as T[U]; + } + } + } + } +} \ No newline at end of file From 3da3a61e20db5cbb9d71393122254d50d8505a84 Mon Sep 17 00:00:00 2001 From: TheMas3212 Date: Mon, 10 Jan 2022 06:58:12 +1100 Subject: [PATCH 2/2] manMax, oneOf, subsetOf validators --- src/Hacknet/HacknetNode.ts | 30 +++------------ src/utils/Validator.ts | 75 +++++++++++++++++++++++++++++++------- 2 files changed, 67 insertions(+), 38 deletions(-) diff --git a/src/Hacknet/HacknetNode.ts b/src/Hacknet/HacknetNode.ts index 9d79f334d..5cb950232 100644 --- a/src/Hacknet/HacknetNode.ts +++ b/src/Hacknet/HacknetNode.ts @@ -18,34 +18,16 @@ import { HacknetNodeConstants } from "./data/Constants"; import { dialogBoxCreate } from "../ui/React/DialogBox"; import { Generic_fromJSON, Generic_toJSON, Reviver } from "../utils/JSONReviver"; -import { ObjectValidator } from "src/utils/Validator"; +import { ObjectValidator, minMax } from "../utils/Validator"; export class HacknetNode implements IHacknetNode { static validationData: ObjectValidator = { - cores: { - default: 1, - min: 1, - max: HacknetNodeConstants.MaxCores - }, - level: { - default: 1, - min: 1, - max: HacknetNodeConstants.MaxLevel - }, - ram: { - default: 1, - min: 1, - max: HacknetNodeConstants.MaxRam - }, - onlineTimeSeconds: { - default: 0, - min: 0 - }, - totalMoneyGenerated: { - default: 0, - min: 0 - } + cores: minMax(1, 1, HacknetNodeConstants.MaxCores), + level: minMax(1, 1, HacknetNodeConstants.MaxLevel), + ram: minMax(1, 1, HacknetNodeConstants.MaxRam), + onlineTimeSeconds: minMax(0, 0, Infinity), + totalMoneyGenerated: minMax(0, 0, Infinity) } // Node's number of cores diff --git a/src/utils/Validator.ts b/src/utils/Validator.ts index a97b5b039..21daba59e 100644 --- a/src/utils/Validator.ts +++ b/src/utils/Validator.ts @@ -2,30 +2,77 @@ export type ObjectValidator = { [key in keyof T]?: ParameterValidator; } -interface ParameterValidator { +interface ParameterValidatorObject { default?: any; min?: number; max?: number; - func?: (obj: T, validator: ObjectValidator, key: U) => void; + func?: (obj: Type, validator: ObjectValidator, key: Key) => void; } +type ParameterValidatorFunction = (obj: Type, key: Key) => void; +type ParameterValidator = ParameterValidatorObject | ParameterValidatorFunction -export function validateObject, U extends keyof T>(obj: T, validator: ObjectValidator): void { - for (const key of Object.keys(validator) as U[]) { +export function validateObject, Key extends keyof Type>(obj: Type, validator: ObjectValidator): void { + for (const key of Object.keys(validator) as Key[]) { const paramValidator = validator[key]; if (paramValidator !== undefined) { - if (paramValidator.func !== undefined) { - paramValidator.func(obj, validator, key); + if (typeof paramValidator === 'function') { + paramValidator(obj, key); } else { - if ((typeof obj[key]) !== (typeof paramValidator.default)) { - obj[key] = paramValidator.default - } - if (typeof obj[key] === 'number' && paramValidator.min !== undefined) { - if (obj[key] < paramValidator.min) obj[key] = paramValidator.min as T[U]; - } - if (typeof obj[key] === 'number' && paramValidator.max !== undefined) { - if (obj[key] > paramValidator.max) obj[key] = paramValidator.max as T[U]; + if (paramValidator.func !== undefined) { + paramValidator.func(obj, validator, key); + } else { + if ((typeof obj[key]) !== (typeof paramValidator.default)) { + obj[key] = paramValidator.default + } + if (typeof obj[key] === 'number' && paramValidator.min !== undefined) { + if (obj[key] < paramValidator.min) obj[key] = paramValidator.min as Type[Key]; + } + if (typeof obj[key] === 'number' && paramValidator.max !== undefined) { + if (obj[key] > paramValidator.max) obj[key] = paramValidator.max as Type[Key]; + } } } } } +} + +export function minMax(def: number, min: number, max: number): (obj: Type, key: Key & keyof Type) => void { + return (obj, key) => { + if (typeof obj[key] !== 'number') { + obj[key] = def as unknown as Type[Key]; + return; + } + if ((obj[key] as unknown as number) < min) { + obj[key] = min as unknown as Type[Key]; + } + if ((obj[key] as unknown as number) > max) { + obj[key] = max as unknown as Type[Key]; + } + }; +} + +export function oneOf(def: Value, options: Value[]): (obj: Type, key: Key & keyof Type) => void { + return (obj, key) => { + if (typeof obj[key] !== typeof def) { + obj[key] = def as unknown as Type[Key]; + return; + } + if (!options.includes(obj[key] as unknown as Value)) { + obj[key] = def as unknown as Type[Key]; + } + }; +} + +export function subsetOf(options: Value[]): (obj: Type, key: Key & keyof Type) => void { + return (obj, key) => { + if (typeof obj[key] !== 'object' || !Array.isArray(obj[key])) { + obj[key] = [] as unknown as Type[Key]; + return; + } + const validValues: Value[] = []; + for (const value of obj[key] as unknown as Value[]) { + if (options.includes(value)) validValues.push(value); + } + obj[key] = validValues as unknown as Type[Key]; + }; } \ No newline at end of file