// ==UserScript==
// @name lib:strict
// @version 3
// @description none
// @run-at document-start
// @author You
// @license GPLv3
// @match *://*/*
// @exclude /livereload.net\/files\/ffopen\/index.html$/
// @icon data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEgAAABICAMAAABiM0N1AAAAAXNSR0IB2cksfwAAAAlwSFlzAAAOxAAADsQBlSsOGwAAAHJQTFRFAAAAEIijAo2yAI60BYyuF4WaFIifAY6zBI2wB4usGIaZEYigIoiZCIyrE4igG4iYD4mjEomhFoedCoqpDIqnDomlBYyvE4efEYmiDYqlA42xBoytD4mkCYqqGYSUFYidC4qoC4upAo6yCoupDYqmCYur4zowOQAAACZ0Uk5TAO////9vr////1+/D/+/L+/Pf/////+f3///////H4////////+5G91rAAACgUlEQVR4nM2Y22KjIBCGidg1264liZqDadK03X3/V2wNKHMC7MpF/xthHD5mgERAqZhWhfYqH6K+Qf2qNNf625hCoFj9/gblMUi5q5jLkXLCKudgyiRm0FMK82cWJp1fLbV5VmvJbCIc0GCYaFqqlDJgADdBjncqAXYobm1xh72aFMflbysteFfdy2Yi1XGOm5HGBzQ1dq7TzEoxjeNTjQZb7VA3e1c7+ImgasAgQ9+xusNVNZIo5xmOMgihIS2PbCQIiHEUdTvhxCcS/kPomfFI2zHy2PkWmA6aNatIJpKFJyekyy02xh5Y3DI9T4aOT6VhIUrsNTFp1pf79Z4SIIVDegl6IJO6cHiL/GimIZDhgTu/BlYWCQzHMl0zBWT/T3KAhtxOuUB9FtBrpsz0RV4xsjHmW+UCaffcSy/5viMGer0/6HdFNMZBq/vjJL38H9Dqx4Fuy0Em12DbZy+9pGtiDijbglwAehyj11n0tRD3WUBm+lwulE/8h4BuA+iWAQQnteg2Xm63WQLTpnMnpjdge0Mgu/GRPsV4xdjQ94Lfi624fabhDkfUqIKNrM64Q837v8yL0prasepCgrtvw1sJpoqanGEX7b5mQboNW8eawXaWXTMfMGxub472hzWzHSn6Sg2G9+6TAyRruE71s+zAzjWaknoyJCQzwxrghH2k5FDT4eqWunuNxyN9QCGcxVod5oADbYnIUkDTGZEf1xDJnSFteQ3KdsT8zYDMQXcHxsevcLH1TrsABzkNPyA/L7b0jg704viMMlpQI96WsHknCt/3YH0kOEo9zcGkwrFK39ck72rmoehmKqo2RKlilzSy/nJKEV45CT38myJp456fezktHjN5aeMAAAAASUVORK5CYII=
// @grant none
// @namespace https://greasyfork.org/users/1184528
// ==/UserScript==
;(() => {
const a = loadlib("allfuncs")
function testformat(
obj,
format,
options = {
allowextras: false,
extrastype: undefined,
allowconversions: false,
logerror: true,
allowunset: false,
throwerror: true,
insideextras: undefined,
},
insideextras
) {
if (options.extrastype) {
if (a(options.extrastype).gettype("string").val) {
options.extrastype = options.extrastype.split("|")
}
}
if (options.allowunset) options.extrastype.push("none")
var isvalid = true
var unsets = []
if (insideextras) {
for (var i = 0; i < Object.keys(obj).length; i++) {
format.push(insideextras)
}
}
format.find(
(
{
name,
prop = name,
type,
inside,
insideoptions = {},
value = [],
insideextras,
},
i
) => {
prop ??= String(i)
if (insideextras) inside ??= []
insideoptions = { ...options, ...insideoptions }
var realvalue = obj[prop]
if (a(type).gettype("string").val) {
type = type.split("|")
}
if (options.allowunset) type.push("none")
if (!a(realvalue).gettype(type).val) {
if (options.allowconversions) {
if (
type.find((type) => {
if (settype.test(realvalue, type)) {
obj[prop] = settype(realvalue, type)
return true
}
})
) {
return false
}
if (type.includes("any")) return false
}
if (!type.includes(obj[prop]))
if (options.logerror) {
if (!(prop in obj))
error(
`the object is missing the property "${prop}". the property "${prop}" should have a type of "${type.join(
"|"
)}"`
)
else
error(
`the value "${prop}" is of type "${a(
obj[prop]
).gettype()}" when it should be a type of "${type.join("|")}"`
)
error("missing or invalid peram type", { prop, obj, type })
}
if (options.throwerror)
throw new Error("missing or invalid peram type")
isvalid = false
return true
}
if (!(prop in obj)) unsets.push(prop)
if (inside) {
if (!testformat(realvalue, inside, insideoptions, insideextras)) {
if (options.logerror) error("invalid inside", { obj })
if (options.throwerror) throw new Error("invalid inside")
isvalid = false
return true
}
}
}
)
var objnames = [...Object.keys(obj), ...unsets]
var formatnames = Object.values(format).map((e, i) => e.name ?? String(i))
var extras = objnames.filter((prop) => !formatnames.includes(prop))
if (!options.allowextras) {
if (extras.length) {
if (options.logerror) {
error(
`the object has the following extra properties: ${JSON.stringify(
extras
)}`
)
error("extras found when allowextras is false", {
obj,
objnames,
formatnames,
})
}
if (options.throwerror)
throw new Error("extras found when allowextras is false")
isvalid = false
}
}
if (extras?.[0]?.name == "__extras") {
var extras = extras[0]
extras.shift()
if (!testformat(obj.inside, obj.__extras, options)) {
isvalid = false
return true
}
}
if (options.extrastype) {
extras.find((prop) => {
if (!a(obj[prop]).gettype(options.extrastype).val) {
if (options.allowconversions) {
if (
options.extrastype.find((type) => {
if (settype.test(obj[prop], type)) {
obj[prop] = settype(obj[prop], type)
return true
}
})
) {
return false
}
if (options.extrastype.includes("any")) return false
}
isvalid = false
return true
}
})
}
return isvalid
}
function strictfunction(func, types, options = {}) {
if (!!options === options) options = { allowconversions: options }
return function (...args) {
if (
!testformat((args = [...args]), types, {
...options,
throwerror: true,
logerror: true,
})
)
throw new Error("error when calling function")
return func.call(this, ...args)
}
}
function setformat(obj, options = {}) {
if (obj[Symbol.for("setformat")]) obj = { ...obj }
return new Proxy(obj, {
get(_obj, prop) {
if (prop == Symbol.for("setformat")) return true
if (prop == Symbol.for("options")) return options
return Reflect.get(_obj, prop)
},
})
}
var entire_object
function newtestformat(
obj,
format,
tempoptions = {
allowextras: false,
extrasformat: {},
functionname: "no name given",
// allowconversions: false,
}
) {
entire_object ??= obj
var extrakeys = []
var options = { ...tempoptions, ...format[Symbol.for("options")] }
for (var objkey of Object.keys(obj)) {
if (!(objkey in format))
if (options.allowextras) extrakeys.push(objkey)
else
throw new Error(
error("object has extra key", {
function_name: options.functionname,
extra_key: objkey,
format,
obj,
entire_object,
})
)
}
for (var [formatkey, formatval] of Object.entries(format)) {
checkformat(formatkey, formatval)
}
function checkformat(formatkey, formatval) {
let currentcomparevalue = obj[formatkey]
if (formatval[Symbol.for("condfunc")])
formatval = formatval(obj, entire_object)
if (!a(obj).gettype(["object", "array"]).val)
throw new Error(
error(`obj is not an object`, {
function_name: options.functionname,
object_is_instead: obj,
trying_to_read_property: formatkey,
entire_object,
})
)
if (!(formatkey in obj))
if (
(formatval && formatval[Symbol.for("optional")]) ||
(formatval.type && formatval.type.includes("none")) ||
(formatval.type && formatval.type.includes("undefined")) ||
(formatval.value && formatval.value.includes(undefined))
)
return
else {
throw new Error(
error(`obj is missing property`, {
function_name: options.functionname,
missing_property: formatkey,
object: obj,
entire_object,
format: { ...format[formatkey] },
})
)
}
if (!formatval[Symbol.for("setformat")]) {
newtestformat(currentcomparevalue, formatval, tempoptions)
} else {
for (var [typekey, currentcheck] of Object.entries(formatval)) {
// log({ obj, currentcomparevalue, typekey, currentcheck, formatval })
switch (typekey) {
case "type":
if (
a(currentcomparevalue).gettype(currentcheck).val ||
currentcheck == "any" ||
currentcheck?.includes?.("any")
)
break
else
throw new Error(
error("type missmatch", {
function_name: options.functionname,
property: formatkey,
object: obj,
entire_object,
current_value: currentcomparevalue,
type_of_current_value: a(currentcomparevalue).gettype().val,
type_should_be: currentcheck,
})
)
case "value":
if (currentcheck.includes(currentcomparevalue)) break
else
throw new Error(
error("value missmatch", {
format_key: formatkey,
object: obj,
entire_object,
function_name: options.functionname,
current_compare_value: currentcomparevalue,
type_of_current_compare_value:
a(currentcomparevalue).gettype().val,
current_check: currentcheck,
})
)
default:
throw new Error(
"invalid key in format",
error({
function_name: options.functionname,
object: obj,
entire_object,
type_key: typekey,
format_val: formatval,
})
)
}
}
}
}
for (var key of extrakeys) {
if (!options.extrasformat)
throw new Error(
`options has allowextras options must also have extrasformat in function [${options.functionname}]`
)
// log(options.extrasformat, options)
checkformat(key, setformat(options.extrasformat), 1)
}
entire_object = undefined
return true
}
loadlib("libloader").savelib("strict", {
strictfunction,
oldtestformat: testformat,
setformat,
optional: function (obj) {
return new Proxy(obj, {
get(_obj, prop) {
if (prop == Symbol.for("optional")) return true
return Reflect.get(_obj, prop)
},
})
},
condfunc: function (func) {
return new Proxy(func, {
get(_obj, prop) {
if (prop == Symbol.for("condfunc")) return true
return Reflect.get(_obj, prop)
},
})
},
testformat: newtestformat,
})
})()