Skip to content
vic

codegram/spex

Validate your Elixir values against value-based schemas

codegram/spex.json
{
"createdAt": "2016-06-03T15:19:10Z",
"defaultBranch": "master",
"description": "Validate your Elixir values against value-based schemas",
"fullName": "codegram/spex",
"homepage": null,
"language": "Elixir",
"name": "spex",
"pushedAt": "2016-06-06T10:53:37Z",
"stargazersCount": 5,
"topics": [],
"updatedAt": "2016-08-08T15:45:14Z",
"url": "https://github.com/codegram/spex"
}

Spex helps you validate your values against value-based schemas.

First, add Spex to your mix.exs

def deps do
[{:spex, "~> 0.1.0"}]
end

Then, update your dependencies:

$ mix deps.get
import Spex

A spec is always a value. Let’s start with the simplest spec, a primitive value:

my_simple_spec = 3
Spex.conforms?(my_simple_spec, 3)
# => true
Spex.conforms?(my_simple_spec, 5)
# => false
Spex.conforms?("foo", "foo")
# => true
Spex.conforms?(:bar, :bar)
# => true

Primitive values, such as integers, strings and atoms, validate values using simple equality.

Let’s move on to a slightly more complex spec: a list. A list spec must have one spec in it, which will validate against all the elements of the validated list.

my_simple_spec = 3
Spex.conforms?([my_simple_spec], [3, 3, 3, 3])
# => true
Spex.conforms?([my_simple_spec], [3, 3, 1, 9])
# => false
Spex.conforms?([my_simple_spec], [])
# => true

This last example might have surprised you — but an empty list always conforms to any list spec. If you want your spec to only validate non-empty lists, we’ve got you covered:

use Spex.DSL
my_simple_spec = 3
Spex.conforms?(non_empty_list(my_simple_spec), [3, 3, 3])
# => true
Spex.conforms?(non_empty_list(my_simple_spec), [])
# => false

Predicate functions (functions that return true or false) are naturally a very useful kind of spec too:

Spex.conforms?(fn(x) -> x > 3 end, 8)
# => true
Spex.conforms?(fn(x) -> x > 3 end, 1)
# => false
import Integer
Spex.conforms?(&Integer.is_odd/1, 3)
# => true

For type predicates, there’s sugar on top of &Kernel.is_x:

use Spex.DSL
Spex.conforms?(number, 3)
# => true
Spex.conforms?(binary, "foo")
# => true
Spex.conforms?(string, "foo") # binary/0 and string/0 are synonms
# => true
Spex.conforms?(bitlist, <<3, 4>>)
# => true

Specs really shine when they can describe arbitrarily nested maps. Map specs to the rescue:

Spex.conforms?(%{person: %{age: fn(x) -> x > 21, name: &Kernel.is_string/1}},
%{person: %{age: 83, name: "James"}})
# => true
Spex.conforms?(%{person: %{age: fn(x) -> x > 21, name: &Kernel.is_string/1}},
%{person: %{age: 10, name: 9}})
# => false

If you’re not interested in the shape of the map, but just more generically its keys and values, you can use map:

Spex.conforms?(map(&Kernel.is_atom/1, &Integer.is_even/1),
%{elapsed: 120, remaining: 90})
# => true
Spex.conforms?(map(&Kernel.is_atom/1, &Integer.is_even/1),
%{elapsed: 3, remaining: 90})
# => false

Tuple specs work pretty much like you’d expect:

Spex.conforms?({3, fn(x) -> x > 3 end}, {3, 4})
# => true
Spex.conforms?({3, fn(x) -> x > 3 end}, {3, 1})
# => false
Spex.conforms?({3, fn(x) -> x > 3 end}, {1, 4})
# => false

Sometimes we don’t care what a value is, as long as it is non-nil. any is just for that:

use Spex.DSL
Spex.conforms?(any, 3)
# => true
Spex.conforms?(any, nil)
# => false

Sometimes we want to validate a value allowing it to be nil. optional is what we want:

use Spex.DSL
Spex.conforms?(optional(3), 3)
# => true
Spex.conforms?(optional(3), nil)
# => true
Spex.conforms?(optional(3), 4)
# => false

Naturally, map specs can describe optional keys:

use Spex.DSL
Spex.conforms?(%{optional(:name) => "foo"}, %{})
# => true
Spex.conforms?(%{optional(:name) => "foo"}, %{name: "bar"})
# => false

And optional values:

Spex.conforms?(%{name: optional("foo")}, %{name: nil})
# => true
Spex.conforms?(%{name: optional("foo")}, %{name: "bar"})
# => false

To make specs easier to use within the rest of your code, you can validate a value against a spec with validate/2:

Spex.validate(3, 3)
# => {:ok, 3}
Spex.validate(3, 4)
# => {:error, "Value doesn't conform to spec: \n\nValue: 4\n\nSpec: 3"}

Or if you’d like to either get the validated value or raise an error, validate!/2 is your friend:

Spex.validate!(3, 3)
# => 3
Spex.validate!(3, 4)
# raises Spex.ValidationError, "Value doesn't conform to spec: \n\nValue: 4\n\nSpec: 3"

Copyright (c) 2016 Codegram. See LICENSE for details.