# StructCop
[](https://travis-ci.org/HarenBroog/struct_cop.svg?branch=master)
[](https://coveralls.io/github/HarenBroog/struct_cop?branch=master)
[](https://hex.pm/packages/struct_cop)
[](https://hex.pm/packages/struct_cop)
StructCop is a library that was aimed to introduce **data correctness** and **type coercion** into Elixir structs. It also simplifies building valid structs with **smart constructors**.
## Getting Started
### Installation
Add `struct_cop`to your list of dependencies in `mix.exs`:
```elixir
def deps do
[
{:struct_cop, "~> 0.2.0"}
]
end
```
### Contract
Define a contract for given entity. We will be describing a single measure for sport activity:
```elixir
defmodule Measurement do
use StructCop
contract do
field :elevation, :float
field :lat, :float
field :lng, :float
field :tracked_at, :utc_datetime_usec
end
def validate(changeset) do
import Ecto.Changeset
changeset
|> validate_required([:tracked_at, :lat, :lng])
|> validate_number(:lat, greater_than_or_equal_to: -90, less_than_or_equal_to: 90)
|> validate_number(:lng, greater_than_or_equal_to: -180, less_than_or_equal_to: 180)
end
end
```
### Usage
From that moment we can:
```elixir
> Measurement.new!(lat: 10, lng: 20, tracked_at: DateTime.utc_now())
%Measurement{
elevation: nil,
lat: 10.0,
lng: 20.0,
tracked_at: ~U[2019-12-28 18:36:01.625656Z]
}
```
It also casts fields from binaries:
```elixir
> Measurement.new!(lat: "52.409538", lng: "16.931992", tracked_at: "2019-12-28T18:30:43.288080+00:00")
%Measurement{
elevation: nil,
lat: 52.409538,
lng: 16.931992,
tracked_at: ~U[2019-12-28 18:30:43.288080Z]
}
```
Passing invalid attributes raises an `ArgumentError`:
```elixir
> Measurement.new!(lat: "not_a_float", lng: 200.10)
** (ArgumentError) cast failed for %Measurement{elevation: nil, lat: nil, lng: nil, tracked_at: nil}:
params:
%{"lat" => "not_a_float", "lng" => 200.1}
errors:
[lng: {"must be less than or equal to %{number}", [validation: :number, kind: :less_than_or_equal_to, number: 180]}, tracked_at: {"can't be blank", [validation: :required]}, lat: {"is invalid", [type: :float, validation: :cast]}]
(struct_cop) lib/struct_cop.ex:47: StructCop.cast!/2
```
## Credits
- [Tobiasz Małecki](https://github.com/amatalai) - discussing initial idea
- [Maciej Kaszubowski](https://github.com/mkaszubowski) - discussing initial idea