local function IsRunningInStudio() return game:GetService("RunService"):IsStudio() end local function assert_(condition, message) if IsRunningInStudio() or _G.__TESTEZ_RUNNING_TEST__ then assert(condition, message) end end local ArgCheck = {} function ArgCheck.isNonNegativeNumber(value, name) -- Temporarily disabled outside of studio/tests. See MOBLUAPP-1161. assert_(typeof(value) == "number" and value >= 0, string.format("expects %s to be a non-negative number!", name)) return value end function ArgCheck.isType(value, expectedType, name) assert_(typeof(value) == expectedType, string.format("expects %s to be a %s! it was: %s", name, expectedType, typeof(value))) return value end function ArgCheck.isInTypes(value, expectedTypes, name) for _, expectedType in ipairs(expectedTypes) do if typeof(value) == expectedType then return value end end assert_(false, string.format("expects %s to be one of expectedTypes! it was: %s", name, typeof(value))) return value end function ArgCheck.isTypeOrNil(value, expectedType, name) assert_(value == nil or typeof(value) == expectedType, string.format("expects %s to be a %s! it was: %s", name, expectedType, typeof(value))) return value end function ArgCheck.isNotNil(value, name) assert_(value ~= nil, string.format("expects %s to be not nil!", name)) return value end function ArgCheck.isNonEmptyString(value, name) assert_(typeof(value) == "string" and value ~= "" , string.format("expects %s to be a non-empty string!", name)) return value end function ArgCheck.isEqual(value, expectedValue, name) assert_(value == expectedValue, string.format("expects %s to equal %s! it was: %s", name, tostring(expectedValue), tostring(value))) return value end -- checks for a number or string representing an integer function ArgCheck.representsInteger(value, name) local numberValue = tonumber(value) assert_(numberValue ~= nil , string.format("expects %s to represent a number!", name)) assert_(numberValue % 1 == 0 , string.format("expects %s to represent an integer!", name)) return value end --[[ Checks if the value matches the given interface iface is the interface description; it can be (in order of priority): * a custom type name: checks against a type from dependencies (see below) * an ArgCheck handler: * "integer" => ArgCheck.representsInteger * "nonEmptyString" => ArgCheck.isNonEmptyString * a lua type (string): equivalent to ArgCheck.isType * a list style table (only first item is considered): checks for a list table with items matching the given interface * a dict style table: checks for a table with keys matching the given interfaces dependencies is a table of named interfaces that can be referenced in iface Example: local myTypes = { Tree = { value = "string", leaves = {"string"}, branches = {"Tree"}, }, } ArgCheck.matchesInterface(someValue, "Tree", "myVal", myTypes) ]]-- function ArgCheck.matchesInterface(value, iface, name, dependencies) if IsRunningInStudio() or _G.__TESTEZ_RUNNING_TEST__ then local checkFnList = { integer = ArgCheck.representsInteger, nonEmptyString = ArgCheck.isNonEmptyString, } if type(iface) == "string" then if dependencies and dependencies[iface] then ArgCheck.matchesInterface(value, dependencies[iface], name, dependencies) else local checkFn = checkFnList[iface] if type(checkFn) == "function" then checkFn(value, name) else ArgCheck.isType(value, iface, name) end end else -- assume iface describes a table (list or dict) ArgCheck.isType(value, "table", name) if iface[1] ~= nil then for index, item in ipairs(value) do ArgCheck.matchesInterface(item, iface[1], name .. "[" .. index .. "]", dependencies) end else for key, desc in pairs(iface) do if string.sub(key, 1, 1) ~= "_" then local itemName = name .. "." .. key local itemValue = value[key] local isRequired = iface._required and iface._required[key] if isRequired or itemValue ~= nil then ArgCheck.matchesInterface(itemValue, desc, itemName, dependencies) end end end end end end return value end function ArgCheck.assert(...) assert_(...) end return ArgCheck