4

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?

asiniy
  • 13,006
  • 7
  • 59
  • 135
  • 1
    `http://vk` is a perfectly valid URL. Do you want to reject URLs where the hostname doesn't contain any "."? – Dogbert Aug 19 '16 at 14:29
  • Why do you think that `http://vk` is a perfect url? Where is the domain name? – asiniy Aug 19 '16 at 14:30
  • 1
    `http://localhost` is a valid URL. http://stackoverflow.com/questions/20573488/why-does-html5-form-validation-allow-emails-without-a-dot – Dogbert Aug 19 '16 at 14:34
  • You're right, but it's theoretically. Practically, have you ever though I need to have `localhost` in production project? :P – asiniy Aug 19 '16 at 14:37
  • 2
    Seems like this was also answered here: http://stackoverflow.com/questions/30696761/check-if-a-url-is-valid-in-elixir – kubajz Aug 20 '16 at 14:04

2 Answers2

14

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]
Dogbert
  • 200,802
  • 40
  • 378
  • 386
  • Nobody offered something else. Sad! But thanks to you! – asiniy Aug 20 '16 at 10:32
  • 1
    This answer has invalid syntax. `uri.scheme != nil` should be `!is_nil(uri.scheme)` or you will encounter a runtime error. – Brian Oct 28 '20 at 18:51
  • @Brian The `&&` operator will return `false` for a `nil` value on the left hand side so just `uri.scheme && ...` suffices too. – Kenny Evitt Nov 16 '20 at 21:18
  • `=~` will also raise if `uri.host` is `nil`, so the complete check should be `not is_nil(uri.scheme) and not is_nil(uri.host) and uri.host =~ "."` – superhawk610 Jul 09 '21 at 16:59
3

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

Tronathan
  • 6,034
  • 4
  • 20
  • 24