0
votes

I am struggling to come up with a clean way to do input validation in Elixir.

In a OOP language i would do something like this:

def my_func(a, b):
  if !validate_a(a):
    raise ArgumentError
  if !validate_b(b):
    raise ArgumentError
  return "something"

The best i could come up with in Elixir is this:

def my_func(a, b) do
  cond do
    not valid_a?(a) -> {:error, "a is invalid"}
    not valid_b?(b) -> {:error, "b is invalid"}
    :otherwise      -> {:ok, "something"}
  end
end

I know that i could also raise an exception in Elxir, but i like pattern-matching against the return type a lot better, since my code doesn't get cluttered with try/rescue blocks.

Is there a preferred pattern to do this in Elixir?

2
Your code looks fine to me. Instead of :otherwise -> for the last case, I've seen most people use true ->.Dogbert
I've seen that too. Personally i prefer :else or :otherwise for readability.Luca Fülbier

2 Answers

4
votes

Everything depends what comes after the validation and what kind of validation you want to have.

For example: if you simply validate is a and b are integers you can use guard clauses in definitions of the functions.

def my_func(a, b) when is_integer(a) and is_integer(b) do
  # your code - validation passed
end

def my_func(_, _) do
  # validation failed
end

In terms of custom validation is good to have separate function and you can use it in your "main" function - your code is cleaner and easier to test, since it's better isolated. In your case - this cond with validation might be in different function.

When dealing with validation in Phoenix controllers you can obviously create own Plugs, which is really handy when you can use like before in Ruby on Rails controllers.

3
votes

Depending on the complexity of your validations and what your data looks like, you could also opt to use Ecto.Changeset even if you do not plan to store the data.

Something really simple like the following

defmodule Foo do
  use Ecto.Schema
  import Ecto.Changeset

  schema "my_schema" do
    field :name, :string
    field :age, :integer
  end

  def changeset(struct, params) do
    struct
    |> cast(params, [:name, :age])
    |> validate_required([:name, :age])
    |> validate_number(:age, greater_than: 18)
  end
end

Once you run the changeset/2 function, you will be given an Ecto.Changeset as a result which has a valid? field. If this is true, it passed all of your validations. If it is false, it will also tell you why from the :errors key from the changeset.