I made this for checking types during runtime. It’s designed specifically for Roblox, but also works in Lua5.1.
Give it a try, let me know if you have any questions or if the documentation could be more clear anywhere.
I made this for checking types during runtime. It’s designed specifically for Roblox, but also works in Lua5.1.
Give it a try, let me know if you have any questions or if the documentation could be more clear anywhere.
Added error messages as a second return value from type checkers.
If you wrap your type check in an assert you’ll get the error message on failure:
assert(t.number("1")) --> number expected, got string
(these can get pretty complex based on your type definitions)
Different example:
local success, errMsg = t.number("1")
if success then
-- do stuff
else
warn(errMsg)
end
this is something ive always wanted to have but ive always been too lazy to make
very good 10/10
How would you check that a number isn’t nan, +inf, or -inf?
(No reason, really. Just thought that someone may need to know this if the domain of a function is all real numbers. I’m using this library for my game <3)
There isn’t a check built in for this, but you could totally make your own. (See tips and tricks in the readme)
I’ll consider adding something like this in the future.
This… is just wonderful.
Thanks, let me know if anything was confusing with the documentation.
Did you find any cool (or not obvious) uses for it?
Not any particularly non-obvious uses, the documentation is good.
Interestingly enough I made a runtime type checker a while ago.
The main difference is that I wanted to bind signatures to functions themselves, here’s an example.
local myFunction = validate("number", "string", function (a, b)
print("Valid")
end)
myFunction(1, "Hi")
-- Valid
myFunction("Test")
-- Error: bad argument #1 to 'myFunction' (number expected, got string)
I realized that overrides were also quite important so added in support for them too.
myFunction:AddSignature("string")
myFunction("Test")
-- Valid
There is also support for varargs, so you can specify the type as ...
and it’ll match any type.
local myOtherFunction = validate("function", "...", function (f, ...)
f(...)
end)
If there’s one thing I dislike about this library, it’s the name. Too short and makes it hard to search for (via Google, DevForum search)
That’s definitely a downside to the name, but many people dislike having a different variable name from the library name. So a single letter encourages a short variable name. More complex type definitions require you to repeat the variable name often in the same line.
Just wanted to dump a bunch of checker I found a use for in my game:
do
local function isNan(n)
return n ~= n
end
function t.nan(value)
local success, errMsg = t.number(value)
if not success then
return false, errMsg or ""
end
if isNan(value) then
return true
else
return false, string.format("NaN expected")
end
end
function t.notNan(value)
local success, errMsg = t.number(value)
if not success then
return false, errMsg or ""
end
if not isNan(value) then
return true
else
return false, string.format("non-NaN expected")
end
end
end
do
local function isFinite(n)
return n > -math.huge and n < math.huge
end
function t.finite(value)
local success, errMsg = t.number(value)
if not success then
return false, errMsg or ""
end
if isFinite(value) then
return true
else
return false, string.format("finite expected")
end
end
function t.infinite(value)
local success, errMsg = t.number(value)
if not success then
return false, errMsg or ""
end
if not isFinite(value) then
return true
else
return false, string.format("infinite expected")
end
end
end
function t.real(value)
local success, errMsg = t.notNan(value)
if not success then
return false, errMsg or ""
end
local success, errMsg = t.finite(value)
if not success then
return false, errMsg or ""
end
return true
end
function t.realVector3(vector)
local success, errMsg = t.Vector3(vector)
if not success then
return false, errMsg or ""
end
local success, errMsg = t.real(vector.magnitude)
if not success then
return false, "vector components are not all real"
end
return true
end
function t.unitVector3(vector)
local success, errMsg = t.Vector3(vector)
if not success then
return false, errMsg or ""
end
if vector.magnitude == 1 then
return true
else
return false, "unit vector expected"
end
end
t.alpha = t.numberConstrained(0, 1)
function t.integerMin(min)
assert(t.integer(min))
return function(value)
local success, errMsg = t.integer(value)
if not success then
return false, errMsg or ""
end
if value >= min then
return true
else
return false, string.format("integer >= %d expected", min)
end
end
end
function t.integerMax(max)
assert(t.integer(max))
return function(value)
local success, errMsg = t.integer(value)
if not success then
return false, errMsg or ""
end
if value <= max then
return true
else
return false, string.format("integer <= %d expected", max)
end
end
end
function t.integerConstrained(min, max)
assert(t.integer(min) and t.integer(max))
local minCheck = t.numberMin(min)
local maxCheck = t.numberMax(max)
return function(value)
local integerSuccess, integerErrMsg = t.integer(value)
if not integerSuccess then
return false, integerErrMsg or ""
end
local minSuccess, minErrMsg = minCheck(value)
if not minSuccess then
return false, minErrMsg or ""
end
local maxSuccess, maxErrMsg = maxCheck(value)
if not maxSuccess then
return false, maxErrMsg or ""
end
return true
end
end
t.nonNegativeInteger = t.integerMin(0)
function t.equals(valueToEquals)
return function(value)
if value == valueToEquals then
return true
end
return false, "two values are not equal."
end
end
function t.notEquals(valueToNotEquals)
return function(value)
if value ~= valueToNotEquals then
return true
end
return false, "two values are not unequal."
end
end
PS: I’m using this library to make sure that clients are not sending bad data to my remotes. Can you think of any reason why I shouldn’t be doing this with your library?
That is a perfectly valid use case! It’s why I originally wrote the library.
And thanks for posting this, these are great examples of custom type checkers.
May need to be a little careful with your unitVector3
- the magnitude of a vector produced by .unit or documented as unitary in the API references is not guaranteed to always equal exactly 1 as far as I’m aware. It may instead be off by a very small amount - you could use an epsilon to catch these.
Do you think vector == vector.unit
is a good way of checking whether or a vector is a unitvector? Would be odd if the value of vector.unit changed every time I got it.
I’m not sure. I’d suggest instead that you just do something like
local abs = math.abs
local eps = 0.0001
-- stuff
return abs(vec.magnitude - 1) < eps
I suppose. I was hoping to avoid using arbitrary eps but then again it’s not like any of my use cases require such precision.
I was able to reproduce this, which makes sense. Not something I’ve thought about.
local vec = Vector3.new(0.0001, 0.0001, 0.0001).Unit
print(vec.Magnitude) -- 0.99999994039536
Wow this is just silly.
local vec = Vector3.new(0.0001, 0.0001, 0.0001).Unit
print(vec.magnitude)
vec = vec.Unit
print(vec.magnitude)
prints
0.99999994039536
1.0000001192093
Doing it again flip-flops between the two values.
Two new functions were added:
t.exactly
can be used to compare a value to another by ==. Pair this with t.union
to create a list of options for a type.
t.strictInterface
which allows interface checking that errors with extra fields.
You can find out more about both of these in the project readme.