Easy, terse, readable and fast function arguments type checking
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Failed to load latest commit information.
debian
rockspecs
rpm
.gitignore
.travis.yml
CHANGELOG.md
LICENSE
Makefile
README.md
checks.lua
tests.lua
travis-run-tests.sh

README.md

Argument type checking API

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.


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.


Description

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.

String type qualifiers

Lua type

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)'

Metatable type

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)'

A type-checking function name

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 to 2^53-1 (inclusive)
    • or lua cdata ctype<uint64_t>
    • or lua cdata ctype<int64_t> in range from 0 to LLONG_MAX

    After the check it is safe to call ffi.cast('uint64_t', ...)

  • checks('int64'):

    • Either integer lua number in range from -2^53+1 to 2^53-1 (inclusive)
    • or lua cdata ctype<uint64_t> in range from 0 to LLONG_MAX
    • or lua cdata ctype<int64_t>

    After the check it is safe to call ffi.cast('int64_t', ...)

  • checks('uuid'):

    • lua cdata ctype<struct tt_uuid> containing uuid_object from tarantool built-in module uuid
  • 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()

Optional type and types combination

Moreover, types can be prefixed with a question mark ?, which makes them optional. For instance, '?table' accepts tables as well as nil values. A '?' type alone accepts anything. It is mainly useful as a placeholder to skip an argument which doesn't need to be checked.

Finally, 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. It can be combined with the question mark, so '?table|number' accepts tables, numbers and nil values.

Question mark is not equivalent to combination with 'nil' type: msgpack.NULL is a valid value for '?number', but not for 'nil|number' combination.

Table type qualifiers

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

When the optional argument is validated with the table type qualifier, its value is set to an empty table. Thus it's safe to do:

function fn(options)
    checks({timeout = '?number'})
    options.timeout = options.timeout or 5.0
end

fn() -- ok

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)'

Variable number of arguments

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)'

Credits

This library was originally a part of luasched. Now it is a dependency for luacheck.