README.md

# istype [![Hex.pm](https://img.shields.io/hexpm/v/istype.svg)](https://hex.pm/packages/istype)

Library that provides parse transforms to enhance Erlang type checking and conversion.

## Usage
1. Add the package to your rebar.config

   ```
   {deps, [
       {istype, "0.1.1"}
   ]}.
   ```
2. Add the parse transform to your rebar.config

   ```
   {erl_opts, [
       {parse_transform, istype_transform}
   ]}.
   ```
3. Use the transforms as documented in their sections below.

## istype
Transform that generates guard friendly type checking statements based on the typespec given. This removes the need for the user to implement and maintain code for type validation.

### Value istype

```
-type timeout() :: integer() || infinity.

%% Before transform
istype(Value, timeout()).

%% After transform
is_integer(Value) orelse Value =:= infinity.
```

The expression generated to validate type is generated in an in order manner.

```
-type timeout0() :: integer() || infinity.
-type timeout1() :: infinity || integer().

%% Before transform
istype(Value0, timeout0()).

istype(Value1, timeout1()).

%% After transform
is_integer(Value0) orelse Value0 =:= infinity.

Value1 =:= infinity orelse is_integer(Value1).
```

### Expression istype

```
-type timeout() :: integer() || infinity.

%% Before transform
istype(expression(), timeout()).

%% After transform
begin
    __IsType_X = expression(),
    is_integer(__IsType_X) orelse __IsType_X =:= infinity
end.
```

When an expression is provided to istype, it is evaluated prior to any checks being processed. This prevents side effects from accidentally processing multiple times. 

```
-type timeout() :: integer() || infinity.

%% Before transform
istype(size(Value), timeout()).

%% After transform
is_integer(size(Value)) orelse size(Value) =:= infinity.
```

Exceptions are made for any BIF that would be allowed in a guard statement. This is to allow the check to remain an allowable guard.

### Tuple istype

```
-type tuple0() :: {atom}.

%% Before transform
istype(Value, tuple0()).

%% After transform
is_tuple(Value) andalso size(Value) =:= 1 andalso element(1, Value) =:= atom.
```

When a type specifies a tuple a specific tuple format as a primitive the arity and field types are also checked.

```
%% Before transform
istype(Value, tuple()).

%% After transform
is_tuple(Value).
```

The `tuple()` type is treated as any tuple.

### Record istype

```
-record(record0, {a :: integer(), b}).

-type record0() :: #record0{}.

%% Before transform
istype(Value, record0()).

%% After transform
is_tuple(Value) andalso size(Value) =:= 3 andalso is_integer(Value#record0.a).
```

As with tuples records are checked against arity and field types.

### Nested types
```
-type timeout() :: integer() | infinity.
-type tuple0() :: {timeout()}.

%% Before transform
istype(Value, tuple0()).

%% After transform
is_tuple(Value) andalso size(Value) =:= 1 andalso (is_integer(element(1, Value)) orelse
                                                   element(1, Value) =:= infinity).
```

## asserttype
Transform that asserts that the value is the specified type. Functionally the same as `true = istype(Value, type()).`

## totype
Transform that enables conversion to custom types. Generates a call to totype:convert/3 with the nested type specs needed for type conversion.

### Value totype
```
-type timeout() :: integer().

1 = totype("1", timeout()).
1 = totype(1.0, timeout()).
1 = totype(<<"1">>, timeout()).
```

Type conversion looks at the input given for the target type and determines how to convert.

```
-type type0() :: binary() | list().
-type type1() :: list() | binary().

<<"1">> = totype(1, type0()).
"1" = totype(1, type1()).
```

When converting to a type that is the union of multiple types the first valid conversion is returned.

### Records totype
```
-record(record0, {a = 0 :: integer()}).
-type record0() :: #record0{}.

#record0{a = 1} = totype([{a, 1}], record0()).
#record0{a = 1} = totype([{"a", "1"}], record0()).
#record0{a = 1} = totype(#{<<"a">> => <<"1">>}], record0()).
```

Records can be generated from maps and proplists. If a map or list contains a key that is not present in the record that value is dropped silently.

```
-record(record0, {a = 0 :: integer()}).
-record(record1, {a = <<"">> :: binary()}).

-type record0() :: #record0{}.
-type record1() :: #record1{}.

#record0{a = 1} = totype(#record0{a = "1"}, record0()).
#record1{a = <<"1">>} = totype(#record0{a = "1"}, record1()).
```

Records can be converted to other records. As with lists and maps only the shared keys are copied.

```
-record(record0, {a = 0 :: integer()}).

#{a => 2} = totype(#record{a = 2}, map()).
[{a, 2}] = totype(#record{a = 2}, list()).
```

Records can also be converted into maps and proplists.