README.md

# RequestValidator

A blazing fast request validator for your phoenix app, which validates a request body before hitting the request handler in the controller.

## Installation

The package can be installed by adding `request_validator` to your list of dependencies in `mix.exs`:

```elixir
def deps do
  [
    {:request_validator, "~> 0.4"}
  ]
end
```

## Basic Usage

```elixir
  defmodule App.UserController do
    use AppWeb, :controller

    plug Request.Validation.Plug, %{
      login: App.Requests.Login,
      register: App.Requests.Registration
    }

    def login(conn, params) do
      ...
    end

    def register(conn, params) do
      ...
    end
  end

  defmodule App.Requests.Registration do
    use Request.Validator
    
    @behaviour Request.Validator

    @impl Request.Validator
    @spec rules(Plug.Conn.t()) :: map()|keyword()
    def rules(_) do
      %{
        "email" => [is_required(), is_email()],
        "name" => [is_required(), is_string()],
        "password" => [is_required(), is_string()],
        "age" => [is_required(), is_numeric(), is_min(18)]
      }
    end

    @impl Request.Validator
    @spec authorize(Plug.Conn.t()) :: boolean()
    def authorize(_), do: true
  end

  defmodule App.Requests.Login do
    use Request.Validator
    
    @behaviour Request.Validator

    @impl Request.Validator
    @spec rules(Plug.Conn.t()) :: map()|keyword()
    def rules(_) do
      %{
        "email" => [is_required(), is_email()],
        "password" => [is_required(), is_string()]
      }
    end

    @impl Request.Validator
    @spec authorize(Plug.Conn.t()) :: boolean()
    def authorize(_), do: true
  end
```

Full documentation can be found at [https://hexdocs.pm/request_validator](https://hexdocs.pm/request_validator).

## Ecto Support

In some cases, your application already makes use of the ecto library. I'm glad to tell you that this library has support when the rule method returns a `Ecto.Changeset` struct, so that you can
make use of the advantages provided by this library without rewritting your validation logic. See example below:

```elixir
defmodule App.Requests.TestRequest do
  use Request.Validator
  use Ecto.Schema

  alias App.Requests.TestRequest

  import Ecto.Changeset

  @behaviour Request.Validator

  embedded_schema do
    field(:name, :string)
    field(:email, :string)
    field(:age, :integer)
    field(:password, :string)
  end

  @doc false
  defp changeset(contact, attrs) do
    contact
    |> cast(attrs, [:name, :email, :age, :password])
    |> validate_required([:name, :email, :age, :password])
    |> validate_number(:age, less_than_or_equal_to: 32)
  end

  @impl Request.Validator
  def rules(conn) do
    %TestRequest{} |> changeset(conn.params)
  end

  @impl Request.Validator
  @spec authorize(Plug.Conn.t())::boolean()
  def authorize(_), do: true
end
```

## Custom Validation Rules

This library provides a variety of helpful rules, however, you might want to define some rules to house your validation logic. To achieve this, you need to create your own rules and validation messages module, see example/steps below;

```elixir
# lib/validations/rules.ex
defmodule App.Validation.Rules do
  alias App.Repo

  require Request.Validator.Helper

  import Ecto.Query
  import Request.Validator.Helper

  with_param(:exists, fn ({model, field}, value, _) ->
    result =
      from(m in model, where: field(m, ^field) == ^value)
      |> Repo.exists?()

    case result do
      true ->
        :ok
      _ ->
        {:error, "This field doesn't exists."}
    end
  end)

  with_param(:unique, fn(params, value, _) ->
    case exists(params).(value, nil) do
      :ok ->
        {:error, "This field has already been taken."}
      {:error, _} ->
        :ok
    end
  end)
end

```

After adding the rule which is a method, then you can make use of the rule in any of your request validator module.

```elixir
  defmodule App.Requests.CourseRegistration do
    use Request.Validator
    
    @behaviour Request.Validator

    @impl Request.Validator
    @spec rules(Plug.Conn.t()) :: map()|keyword()
    def rules(_) do
      %{
        "email" => [is_required(), is_email()],
        "name" => [is_required(), is_string()],
        "age" => [is_required(), is_numeric(), is_min(18)],
        "course" => [is_required(), is_string(), exists({App.Course, :id})]
      }
    end

    @impl Request.Validator
    @spec authorize(Plug.Conn.t()) :: boolean()
    def authorize(_), do: true
  end
```

**NB:** You should use the `define_rule` macro if your validation function doesn't accept argument like the `exists` rule. And you can also define a function directly and reference using *function capturing*, e.g. `&rule_validator/2`.

## Rules

Below is a list of all available validation rules and their function:

### bail

Stop running validation rules after the first validation failure. Example, `bail([is_required(), is_string()])`

### is_confirmed()

The field under validation must have a matching field of `bar_confirmation`. For example, if the field under validation is `password`, a matching `password_confirmation` field must be present in the input.


### is_email

The field under validation must be formatted as an e-mail address

### is_gt(*field*)

The field under validation must be greater than the given field. The two fields must be of the same type

### is_lt(*field*)

The field under validation must be less than the given *field*. The two fields must be of the same type

### is_max(*value*)

The field under validation must be less than or equal to a maximum *value*. Supported types are strings, numerics and list.

### is_min(*value*)

The field under validation must have a minimum *value*. Supported types are strings, numerics and list.

### is_numeric()

The field under validation must be numeric.

### is_required()

The field under validation must be present in the input data and not empty. A field is considered "empty" if one of the following conditions are true:

- The value is nil.
- The value is an empty string.
- The value is an empty list or map.

### is_string()

The field under validation must be a string.

## TODOS

- [ ] Include more validation rules
- [ ] Norm validation support
- [x] Ecto schema support

## License

RequestValidator is released under the MIT License - see the [LICENSE](LICENSE) file.