README.md

# Pharams

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

**WORK IN PROGRESS**

Define Phoenix parameter validators declaratively. Under the hood, the Pharams macros make use of Ecto.Schema and as a results, errors are provided in the form of changesets. These error changesets can then be sent to any error view of your choosing (Pharams comes with a very basic error view out of the box). In addition, the usage of the `pharams` macro will inject a Plug in your controller so that the `params` variable passed to your Phoenix controller action function has already already been validated. The `params` variable passed to your function controller is a map which mirrors your schema with atoms as keys.

## Installation

[Available in Hex](https://hex.pm/packages/pharams), the package can be installed
by adding `pharams` to your list of dependencies in `mix.exs`:

```elixir
def deps do
  [
    {:pharams, "~> 0.3.0"}
  ]
end
```

If you would like the formatter to not add parenthesis to `required` and `optional` you can add the following to you `.formatter.exs`:

```
[
  import_deps: [:pharams]
]
```

Documentation can be found at [https://hexdocs.pm/pharams](https://hexdocs.pm/pharams).

## Usage

To get started, add the following to your Phoenix Controller:

```elixir
use Pharams
```

If you would like to configure the Pharams module to use a different error view or status code you can do the following (shown with defaults):

```elixir
# error_status takes an atom (see https://github.com/elixir-plug/plug/blob/master/lib/plug/conn/status.ex for full list of supported statuses)
use Pharams, view_module: Pharams.ErrorView, view_template: "errors.json", error_status: :unprocessable_entity
```

Next you can use the `pharams` macro to define the validator that should be used against incoming requests. Below is a simple example:

```elixir
pharams :index do
  required :terms_conditions, :boolean
  required :password, :string
  required :password_confirmation, :string
  optional :age, :integer
end

def index(conn, params) do
  # You will only get into this function if the request
  # parameters have passed the above validator. The params
  # variable is now just a map with atoms as keys.

  render(conn, "index.html")
end
```

Let's break down what is happening here. Before the `do` block, we specify to which action the validator will apply. In this case the `:index` atom is provided because we want the validation to take place against the `def index` action in the controller. Next, we define which fields are required/optional in the request, and what their types are. Simple enough so far :), let's look at a more complex example.

In the following example, we will create a validator with a bit more functionality:

```elixir
pharams :index do
  required :terms_conditions, :boolean, acceptance: []
  required :password, :string, confirmation: [], length: [min: 8, max: 16]
  required :password_confirmation, :string, []
  required :age, :integer, number: [greater_than: 16, less_than: 110]

  optional :interests, {:array, :string},
    subset: ["art", "music", "technology"],
    length: [max: 2]

  optional :favorite_programming_language, :string,
    exclusion: ~w(Java Perl PHP),
    default: "Elixir"

  required :addresses, :one do
    required :billing_address, :one do
      required :street_line_1, :string
      optional :street_line_2, :string
      required :zip_code, :string, format: ~r/\d{5}/

      required :coordinates, :one do
        required :lat, :float
        required :long, :float
      end
    end

    required :shipping_address, :one do
      required :street_line_1, :string
      optional :street_line_2, :string
      required :zip_code, :string, format: ~r/\d{5}/

      required :coordinates, :one do
        required :lat, :float
        required :long, :float
      end
    end
  end
end

def index(conn, params) do
  # You will only get into this function if the request
  # parameters have passed the above validator. The params
  # variable is now just a map with atoms as keys.

  render(conn, "index.html")
end
```

Let's break this one down as well to make things clearer. As you can see, you can also have embedded `required` and `optional` schemas in your validator declaration (for example the `:addresses` schema contains both `:billing_address` and `:shipping_address` schemas). You can also pass in a keyword list of additional options for fields. For example the `:favorite_programming_language` field can have a default value of `Elixir`, and using the `Ecto.Changeset.validate_exclusion/4` via the `exclusion: ~w(Java Perl PHP)` keyword entry, you can validate that the string is not in the provided list. All of the `Ecto.Changeset.validate_*` functions are supported, with the caveat that you call them without their `validate_` prefix. You can also get fairly involved with your validations using the `validate_change` Ecto.Changeset function:

```elixir
pharams :index do
  required(:rand, :string,
    change: [
      :something,
      fn :rand, rand ->
        if rand == "foo" do
          [rand: "cannot be foo"]
        else
          []
        end
      end
    ]
  )
end
```

When using the `Ecto.Changeset.validate_*/3` functions, all you have to provide is the last argument. The Pharams macro will populate the first two parameters under the hood for you. When using the `Ecto.Changeset.validate_*/4` functions, you have to pass the last two parameters as a list. See below the two different uses of `validate_subset`:

```elixir
# Using Ecto.Changeset.validate_subset/3
optional(:interests, {:array, :string},
  subset: ["art", "music", "technology"],
  length: [max: 2]
)

# Using Ecto.Changeset.validate_subset/4
optional(:interests, {:array, :string},
  subset: [["art", "music", "technology"], [message: "Only 3 cool interests to pick from!"]],
  length: [max: 2]
)
```

Another thing to note in this example is that multiple `Ecto.Changeset.validate_*` functions can be applied to a specific field. In this example, both `validate_length` and `validate_subset` are used.