This library declares a checks()
function and a checkers
table, which
allow to check the parameters passed to a Lua function in a fast and
unobtrusive way.
Starting with 2.11.0, Tarantool embeds the checks
module.
You can learn more about the module's API from the documentation page.
COGNIZE THE ESSENCE
It is NOT designed to validate user input. It is designed to provide better type checking of function arguments.
It is NOT designed to hide your problems. It is designed to reveal mistakes in code.
You should think of checks
as of assert
on steroids.
Function checks(type_1, ..., type_n)
,
when called directly inside function fn
,
checks that fn
's 1st argument conforms to type_1
,
2nd argument conforms to type_2
, etc.
Type specifiers are strings or tables, and if the arguments passed
to fn
don't conform to their specification, a proper error message is produced,
pinpointing the call to fn
as the faulty expression.
The type is simply type(arg)
, such as 'table'
, 'number'
etc.
function fn_string(x)
checks('string')
end
fn_string('foo') -- ok
fn_string(99) -- error: bad argument #1 to fn_string (string expected, got number)'
An arbitrary name, which is stored in the __type
field of the argument metatable
function fn_color(x)
checks('color')
end
local blue = setmetatable({0, 0, 255}, {__type = 'color'})
fn_color(blue) -- ok
fn_color({}) -- error: bad argument #1 to fn_color (color expected, got table)'
The function would be stored in the checkers
global table.
This function is called with original value passed to fn
and must return true
if the value is valid.
function fn_positive(x)
checks('positive')
end
function checkers.positive(p)
return (type(p) == 'number') and (p > 0)
end
fn_positive(42) -- ok
fn_positive(-1) -- error: bad argument #1 to fn_positive (positive expected, got number)'
One can use built-in checkers:
-
checks('uint64')
:- Either integer lua number in range from
0
to2^53-1
(inclusive) - or lua cdata
ctype<uint64_t>
- or lua cdata
ctype<int64_t>
in range from0
toLLONG_MAX
After the check it is safe to call
ffi.cast('uint64_t', ...)
- Either integer lua number in range from
-
checks('int64')
:- Either integer lua number in range from
-2^53+1
to2^53-1
(inclusive) - or lua cdata
ctype<uint64_t>
in range from0
toLLONG_MAX
- or lua cdata
ctype<int64_t>
After the check it is safe to call
ffi.cast('int64_t', ...)
- Either integer lua number in range from
-
checks('uuid')
:- lua cdata
ctype<struct tt_uuid>
containinguuid_object
from tarantool built-in module uuid
- lua cdata
-
checks('uuid_str')
:- uuid as a 36-byte hexadecimal string
After the check it is safe to call
uuid.fromstr()
-
checks('uuid_bin')
:- uuid as a 16-byte binary string
After the check it is safe to call
uuid.frombin()
-
checks('tuple')
:- Check that specified value is tuple
-
checks('decimal')
:- Check that specified value is decimal
(Available since Tarantool 2.4)
Moreover, several types can be accepted
if their names are concatenated with a bar |
between them.
For instance, 'table|number'
accepts tables as well as numbers.
Finally, string type qualifier can be prefixed
with a question mark ?
, which makes them optional.
For instance, '?table'
accepts tables as well as nil
values,
'?table|number'
accepts tables, numbers and nil values.
Question mark is not an equivalent to combination with the 'nil'
type:
box.NULL
is a valid value for '?number'
, but not for 'nil|number'
combination.
A '?'
type alone accepts anything. It is mainly useful as a placeholder
to skip an argument which doesn't need to be checked.
To sum up, the string type qualifier has the following syntax:
either '[?]type1[|type2[...]]'
or single '?'
.
The type qualifier may be a table.
In this case the argument is checked to conform to '?table'
type, and its content is validated.
Table values are validated against type qualifiers as described above.
Table keys, which are not mentioned in checks
, are validated against 'nil'
type.
Table type qualifiers may be recursive and use tables too.
function fn_opts(options)
checks({
my_string = '?string',
my_number = '?number',
})
end
fn_opts({my_string = 's'}) -- ok
fn_opts({my_number = 101}) -- ok
fn_opts({my_number = 'x'}) -- error: bad argument options.my_number to fn_opts (?number expected, got string)'
fn_opts({bad_field = true}) -- error: unexpected argument options.bad_field to fn_opts
Since v3.0 checks
does not modify any arguments. Be careful when indexing options table:
function fn_bad(options)
checks({timeout = '?number'})
print(options.timeout)
end
fn_bad() -- error: attempt to index local 'options' (a nil value)
function fn_good(options)
checks({timeout = '?number'})
local timeout = options and options.timeout or default_value
print(timeout)
end
fn_good() -- ok, prints default_value
To keep backward compatibility you can use the flag _G._checks_v2_compatible = true
.
This will substitute nil
arguments with an empty table (as it used to be in v2.1).
_G._checks_v2_compatible = true
local checks = require('checks')
local json = require('json')
function fn_v2_compatible(options)
checks({timeout = '?number'})
print(options.timeout)
end
fn_v2_compatible() -- ok, prints "nil"
When an argument inside table type qualifier is specified without question mark (i.e. not optional), whole table becomes mandatory:
function fn(options)
checks({
req_string = 'string',
})
end
fn() -- error: bad argument options.req_string to fn (string expected, got nil)'
Functions with variable number of arguments are supported:
function fn_varargs(arg1, ...)
checks('string')
end
fn_varargs('s') -- ok
fn_varargs('s', 1) -- ok
fn_varargs('s', {}) -- ok
fn_varargs(42) -- error: bad argument #1 to fn_varargs (string expected, got number)'
This library was originally a part of luasched. Now it is a dependency for luacheck.