How to do a series of validations in Elixir the most idiomatic way

Below is an example to use with. Each validation step only return ok/error tuple. The url is not changed so it can be reused in each step.

@spec validate_url(raw_url :: URI.t() | binary) :: {:ok, URI.t()} | :error
def validate_url(raw_url) do
  with url <- URI.parse(raw_url),
       :ok <- validate_path(url),
       :ok <- validate_host(url) do
    {:ok, url}
  else
    :error -> :error
  end
end

@spec validate_path(URI.t()) :: :ok | :error
defp validate_path(%URI{path: path}) do
  # ...
end

@spec validate_host(URI.t()) :: :ok | :error
defp validate_host(%URI{host: host}) do
  # ...
end

Pipeline is suitable when you have a data structure to express both url and validation result. The simplest data structure is {:ok, url} | :error .