# Shapex
Shapex is a small library to define contracts for a maps and validate them easily.
Key features:
- Validate BEAM specific data types like `atom`
- Define custom data types easily with Shapex.Type behaviour. Documentation coming soon.
## Future plans
- [ ] Add macro to make defining schema easier
- [ ] Add more built-in types
- [ ] Date
- [ ] Time
- [ ] DateTime
- [ ] Add fast mode, wich will validate until first error and return it
## Example
```elixir
defmodule UserValidator do
alias Shapex.Types, as: S
# or import Shapex.Types
@user_schema S.map(%{
name: S.string(min_length: 3),
age: S.integer(gte: {18, "Should be adult"}),
email: S.string(regex: ~r/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/),
address:
S.map(%{
street: S.string(),
city: S.string(),
zip: S.string(min_length: 5)
}),
role: S.atom(eq: :admin)
})
def validate_user(user_params) do
case Shapex.validate(@user_schema, user_params) do
:ok -> insert_user(user)
{:error, errors} ->
Log.error("Validation failed", %{errors: errors})
{:error, errors}
end
end
end
user = %{
name: "John Doe",
age: 17,
email: "john@google.com",
address: %{
street: "123 Main St",
city: "New York",
zip: "000504"
},
role: :member
}
UserValidator.validate_user(user)
# {:error, %{age: %{gte: "Should be adult"}, role: %{eq: "Should be :admin"}}
```
## Schema DSL
This DSL is used to simplify the creation of the contract that will be used for data validation.
Here is an example of how it simplifies code.
**Function composition:**
```elixir
alias Shapex.Types, as: S
animal_schema = S.enum([
S.map(%{
name: S.string(min_length: 2),
family: S.atom(eq: :dog),
breed: S.enum([S.string(eq: "Akita"), S.string(eq: "Husky"), S.string(eq: "Poodle")])
}),
S.map(%{
name: S.string(min_length: 2),
family: S.atom(eq: :cat),
breed: S.enum([S.string(eq: "Siamese"), S.string(eq: "Persian"), S.string(eq: "Maine Coon")])
}),
S.map(%{
name: S.string(min_length: 2),
family: S.atom(eq: :owl),
genus: S.enum([S.string(eq: "Athene"), S.string(eq: "Bubo"), S.string(eq: "Strix")])
})
])
```
**Schema DSL:**
```elixir
require Shapex
animal_schema = Shapex.schema(
%{
name: string(min_length: 2),
family: :dog,
breed: "Akita" | "Husky" | "Poodle"
}
| %{
name: string(min_length: 2),
family: :cat,
breed: "Siamese" | "Persian" | "Maine Coon"
}
| %{
name: string(min_length: 2),
family: :owl,
genus: "Athene" | "Bubo" | "Strix"
}
)
```
As you can see you can use built-in function without any import, since they are supported by the DSL. They copy API of `Shapex.Types` module functions.
### DSL Cheatsheet
The differences between schema DSL and default function composition style are:
| Type | DSL expression | Function Composition |
|---|---|---|
| Integer | `1` | `integer(eq: 1)` |
| Float | `1.0` | `float(eq: 1.0)` |
| Boolean | `true` | `boolean(true)` |
| Atom | `:atom` | `atom(eq: :atom)` |
| String | `"name"` | `string(eq: "name")` |
| Enum | `1 | 2 | 3` | `enum([integer(eq: 1), integer(eq: 2), integer(eq: 3)])` |
| Map | `%{name: "John Doe"}` | `map(key: string(eq: "John Doe"))` |
## What is not planned
- Generic Tuple type, since it's not very clear how to validate them the best, so if you need to validate a tuple, please create custom type for it.