Skip to content
/ rtypes Public

Automatically generate run time type checkers in Elixir

License

Notifications You must be signed in to change notification settings

d2km/rtypes

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

64 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

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 the 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])

Data Generators

The library provides Generator module which defines the make/4 function and make/2 macro to be used with property-based frameworks. The StreamData backend is provided with the library, while PropCheck backend can be found in rtypes_propcheck library.

For example, to write a unit test for a pure function with a given spec to use with StreamData framework one could write something along the lines:

defmodule MyTest do
  use ExUnit.Case
  use ExUnitProperties

  require RTypes
  require RTypes.Generator, as: Generator

  # @spec f(arg_type) :: result_type
  arg_type_gen = Generator.make(arg_type, Generator.StreamData)
  result_type? = RTypes.make_predicate(result_type)

  property \"for any parameter `f/1` returs value of `result_type`\" do
    check all value <- arg_type_gen do
      assert result_type?.(f(value))
    end
  end
end

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 DONE.

  • Better error messages.

About

Automatically generate run time type checkers in Elixir

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published