lib/confispex/type/url.ex

defmodule Confispex.Type.URL do
  @moduledoc """
  An URL type.

  Returns input string if it is a valid URL.

  No options.

  ## Examples

      iex> Confispex.Type.cast("postgres://user@pass:host", Confispex.Type.URL)
      {:ok, "postgres://user@pass:host"}

      iex> Confispex.Type.cast("localhost", Confispex.Type.URL)
      {:error, {"localhost", Confispex.Type.URL, [validation: "missing a scheme (e.g. https)"]}}
  """
  @behaviour Confispex.Type

  @impl true
  def cast(value, _opts) when is_binary(value) do
    case URI.parse(value) do
      %URI{scheme: nil} ->
        {:error, validation: "missing a scheme (e.g. https)"}

      %URI{host: host} when host in [nil, ""] ->
        {:error, validation: "missing a host"}

      %URI{query: query} when is_binary(query) ->
        try do
          URI.decode_query(query)
          {:ok, value}
        rescue
          ArgumentError ->
            {:error, parsing: "malformed query string"}
        end

      _ ->
        {:ok, value}
    end
  end
end