5

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?

Alex Antonov
  • 14,134
  • 7
  • 65
  • 142
  • 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? – Alex Antonov 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 – Alex Antonov 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
  • About the question "Why do you think that http://vk is a perfect url? Where is the domain name? " : the domain name in the URL is "vk" (or "vk." in FQDN form). Nobody ever said that domain names must have more than one label. – bortzmeyer Feb 18 '23 at 11:15

3 Answers3

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
  • 212,659
  • 41
  • 396
  • 397
  • Nobody offered something else. Sad! But thanks to you! – Alex Antonov 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
4

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

Bruno Paulino
  • 5,611
  • 1
  • 41
  • 40
Tronathan
  • 6,473
  • 5
  • 22
  • 24
1

Since elixir 1.13.0, way to validate a url is using URI.new/1. It will take the url string, parses and validates it and returns %{:ok, url} if it is valid, else, it returns %{:error, invalid_part_of_the_url} for invalid url. Then one can validate in more granular level using pattern matching of the resulting {:ok, struct}. For more info, in IEx, run h URI.new.