defmodule NLdoc.Ecto.Validator.URI do @moduledoc """ Validator for URI's. ## Example defmodule User do use Ecto.Schema import NLdoc.Ecto.Validator.URI, only: [:validate_uri: 3] schema "users" do field :name, :string field :profile_url, :string end def changeset(user, params) do user |> Ecto.Changeset.cast(params, [:name, :profile_url]) |> validate_uri(:profile_url, schemes: ["https"]) end end """ @type validate_url_opt() :: {:schemes, [String.t()]} @spec validate_uri( changeset :: Ecto.Changeset.t(), field :: atom(), opts :: [validate_url_opt()] ) :: Ecto.Changeset.t() def validate_uri(changeset, field, opts \\ []) do allowed_schemes = Keyword.get(opts, :schemes, []) Ecto.Changeset.validate_change( changeset, field, fn ^field, value -> case do_validate_uri(value, allowed_schemes) do :ok -> [] {:error, reason} -> [{field, reason}] end end ) end @spec do_validate_uri(value :: binary(), allowed_schemes :: [String.t()]) :: :ok | {:error, String.t()} def do_validate_uri(value, allowed_schemes) when is_binary(value) do case URI.new(value) do {:ok, %URI{scheme: scheme}} -> cond do is_nil(scheme) -> {:error, "must have scheme"} Enum.empty?(allowed_schemes) -> :ok Enum.member?(allowed_schemes, scheme) -> :ok true -> {:error, "has invalid scheme"} end {:error, _} -> {:error, "must be a URI"} end end end