Skip to content
master
Switch branches/tags
Code

Latest commit

 

Git stats

Files

Permalink
Failed to load latest commit information.
Type
Name
Latest commit message
Commit time
 
 
 
 
lib
 
 
src
 
 
 
 
 
 
 
 
 
 
 
 
 
 

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

Releases

No releases published

Packages

No packages published