Skip to content
Automatically generate run time type checkers in Elixir
Branch: master
Clone or download
d2km Merge pull request #3 from d2km/api-change
Fix bug in boolean() type validator
Latest commit 0ba55e2 Jun 13, 2019
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
bench Change names to make them sound more natural Jun 9, 2019
config
lib Fix bug in boolean() type validator Jun 13, 2019
test Change names to make them sound more natural Jun 9, 2019
.formatter.exs
.gitignore
LICENSE
README.md Change names to make them sound more natural Jun 9, 2019
mix.exs Bump version to 0.2.2 Jun 13, 2019
mix.lock

README.md

RTypes

RTypes is an Elixir library which helps automatically create a validation function for a given user type. The function can be used to check the shape of the data after de-serialisation or in unit-tests.

Let's suppose we have a type

@type t :: 0..255

and we have a value x. To ensure that our value corresponds to the type t we can use the function

def is_t(x) when is_integer(x) and x >= 0 and x <= 255

Now, if we have a compound type

@type list_of_ts :: [t]

and a value xs, we can use is_list/1 guard on xs and then ensure that all elements of the list conform to t. And if we have a more complex structure

@type state(a, b) :: %{key1: {a, b}, key2: list_of_ts()}

and a value s, we can check that s is a map which has keys key1 and key2, apply the logic above for the value of key2 and for any concrete types a and b we can check that he value of key1 is a tuple of length 2 and its elements conform to a and b respectively. So we just recursively apply those checks.

That's the gist of it.

Usage

The library defines make_validator/1 and make_predicate/1 macros, and make_validator/3 and make_predicate/3 functions which can be used to build the functions at run time. The difference between the two is that a validator returns :ok or {:error, reason} where reason explains what went wrong, while a predicate returns only true or false.

iex> require RTypes
iex> port_number? = RTypes.make_predicate(:inet.port_number())
iex> port_number?.(8080)
true
iex> port_number?.(80000)
false
iex> validate_is_kwlist = RTypes.make_validator(Keyword, :t, [{:type, 0, :pos_integer, []}])
iex> validate_is_kwlist.(key1: 4, key2: 5)
:ok
iex> {:error, _reason} = validate_is_kwlist.([1, 2, 3])

Implementation

A generated validation function is essentially a walk-the-tree interpreter of the expanded AST that represents the type. However, instead of evaluating the AST it applies basic type checks.

A generated predicate uses a different approach. It builds up a chain of suspended function calls (closures) which mirrors the type's AST. The benchmarks in bench/ directory have shown that it works approximately 2x faster than the interpreted version. The downside is that it provides no explanation or failed cases.

Notes

  • The type must be fully instantiated, that is, all the type parameters should be of a concrete type.

  • For practical reasons the generated function does not recurse down to iolist(), making only some simplified tests.

TODO

  • Handle recursive types.

  • Data generator.

  • Better error messages.

You can’t perform that action at this time.