Alex Antonov
Alex Antonov

Reputation: 15146

How to validate url in elixir?

I want to validate uris like:

http://vk.com
http://semantic-ui.com/collections/menu.html
https://translate.yandex.ru/?text=poll&lang=en-ru

and not

www.vk.com
abdeeej
http://vk

but haven't found either package of native code implementation for it.

How can I do that?

Upvotes: 7

Views: 5071

Answers (3)

Aparigraha
Aparigraha

Reputation: 177

Since elixir 1.13.0, another way to validate a url is using URI.new/1. It errors on poorly encoded urls and more stricter compared to URI.parse

Caution: URI.new/1 fails on poorly encoded or not encoded url such as url with a space in it which is not encoded and returns error.

# Without encoding, notice the space in the url
iex(1)> URI.new("https://testdomain/Teams/Odense Boldklub.png")
{:error, ":"}

# After encoding URL
iex(9)> URI.encode("https://testdomain/Teams/Odense Boldklub.png") |> URI.new
{:ok,
 %URI{
   scheme: "https",
   userinfo: nil,
   host: "testdomain",
   port: 443,
   path: "/Teams/Odense%20Boldklub.png",
   query: nil,
   fragment: nil
 }}

Upvotes: 2

Tronathan
Tronathan

Reputation: 6784

This solution is more complete:

  def validate_url(changeset, field, opts \\ []) do
    validate_change(changeset, field, fn _, value ->
      case URI.parse(value) do
        val(%URI{scheme: nil}) ->
          "is missing a scheme (e.g. https)"

        %URI{host: nil} ->
          "is missing a host"

        %URI{host: host} ->
          case :inet.gethostbyname(Kernel.to_charlist(host)) do
            {:ok, _} -> nil
            {:error, _} -> "invalid host"
          end
      end
      |> case do
        error when is_binary(error) -> [{field, Keyword.get(opts, :message, error)}]
        _ -> []
      end
    end)
  end

Cribbed from https://gist.github.com/atomkirk/74b39b5b09c7d0f21763dd55b877f998

Upvotes: 4

Dogbert
Dogbert

Reputation: 222080

All of those are technically valid URLs according to the spec so URI.parse/1 returns a %URI{} struct for all, but if you want to reject domains without a scheme and a dot in the host, you can do:

valid? = fn url ->
  uri = URI.parse(url)
  uri.scheme != nil && uri.host =~ "."
end

Test:

urls = [
  "http://vk.com",
  "http://semantic-ui.com/collections/menu.html",
  "https://translate.yandex.ru/?text=poll&lang=en-ru",
  "www.vk.com",
  "abdeeej",
  "http://vk"
]

urls |> Enum.map(valid?) |> IO.inspect

Output:

[true, true, true, false, false, false]

Upvotes: 15

Related Questions