Skip to content

Commit 382ad27

Browse files
authored
Merge pull request #2 from mazingstudio/dev
Handle more cases
2 parents 59e06e0 + 71d45e8 commit 382ad27

File tree

5 files changed

+98
-18
lines changed

5 files changed

+98
-18
lines changed

README.md

+6
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,12 @@ Norma depends heavily on the implementation of the `URI` module (part of the sta
3131
iex> Norma.normalize("mazing.studio")
3232
> "http://mazing.studio"
3333

34+
iex> Norma.normalize_if_valid("mazing.studio")
35+
> {:ok, "http://mazing.studio"}
36+
37+
iex> Norma.normalize_if_valid("mazing")
38+
> {:error, "Not an URL."}
39+
3440
iex> options = %{remove_fragment: true, force_root_path: true, remove_www: true}
3541
iex> Norma.normalize("//www.mazing.studio:1337/test#test", options)
3642
> "http://mazing.studio/"

lib/norma.ex

+56-2
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,66 @@ defmodule Norma do
1515
## Examples
1616
1717
iex> Norma.normalize("//www.mazing.studio", %{remove_www: true})
18-
"http://mazing.studio"
18+
{:ok, "http://mazing.studio"}
1919
2020
"""
21+
def normalize_if_valid(url, options \\ %{}) do
22+
if is_url?(url) do
23+
normalized_url = url
24+
|> safe_parse
25+
|> Normalizer.normalize(options)
26+
{:ok, normalized_url}
27+
else
28+
{:error, "Not an URL."}
29+
end
30+
end
31+
32+
@doc """
33+
Similar to `normalize_if_valid/2`, but will return the given string unchanged
34+
if it's not an URL.
35+
36+
## Examples
37+
38+
iex> Norma.normalize!("//www.mazing.studio", %{remove_www: true})
39+
"http://mazing.studio"
40+
"""
2141
def normalize(url, options \\ %{}) do
42+
case normalize_if_valid(url, options) do
43+
{:ok, normalized_url} -> normalized_url
44+
{:error, _} -> url
45+
end
46+
end
47+
48+
@doc """
49+
Solve an issue related to the regex provided by the URI spec
50+
(see https://tools.ietf.org/html/rfc3986#appendix-B).
51+
52+
If trying to parse from string to %URI{} something like "mazing.studio:80",
53+
the result will be:
54+
%URI{scheme: "mazing.studio", path: "21", host: nil}
55+
_(Other keys skipped for brevity, but their value is `nil`.)_
56+
57+
But "//mazing.studio:80", will be parsed correctly:
58+
%URI{host: "mazing.studio", authority: "mazing.studio:80", port: 80}
59+
"""
60+
defp safe_parse(url) do
2261
url
2362
|> URI.parse
24-
|> Normalizer.normalize(options)
63+
|> has_valid_host?
2564
end
65+
66+
defp has_valid_host?(url = %URI{host: nil}) do
67+
url = url |> URI.to_string
68+
69+
"//" <> url
70+
|> safe_parse
71+
end
72+
73+
defp has_valid_host?(url = %URI{host: host})
74+
when host != nil, do: url
75+
76+
@doc """
77+
This sure looks dumb, but a valid host will normally have at least a dot.
78+
"""
79+
defp is_url?(url), do: url |> String.contains?(".")
2680
end

lib/norma/utils.ex

+19-10
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
defmodule Norma.Utils do
2-
def port_handler (port) do
2+
@masked_ports ["443", "80", "8080", "21"]
3+
4+
def port_handler(port) do
35
case port do
46
443 -> "https"
57
80 -> "http"
@@ -24,15 +26,17 @@ defmodule Norma.Utils do
2426
Help would be appreciated here to solve it better.
2527
"""
2628
def form_url(%URI{host: host,
27-
path: path,
28-
scheme: scheme,
29-
query: query,
30-
fragment: fragment}) do
31-
form_scheme(scheme) # "http://"
32-
|> safe_concat(host) # "http://google.com"
33-
|> safe_concat(path) # "http://google.com/test"
34-
|> Kernel.<>(form_fragment(fragment)) # "http://google.com/test#cats"
35-
|> Kernel.<>(form_query(query)) # "http://google.com/test#cats?dogs_allowed=ño"
29+
path: path,
30+
scheme: scheme,
31+
query: query,
32+
port: port,
33+
fragment: fragment}) do
34+
form_scheme(scheme) # "http://"
35+
|> safe_concat(host) # "http://google.com"
36+
|> Kernel.<>(form_port(port |> to_string)) # "http://google.com:1337"
37+
|> safe_concat(path) # "http://google.com:1337/test"
38+
|> Kernel.<>(form_fragment(fragment)) # "http://google.com:1337/test#cats"
39+
|> Kernel.<>(form_query(query)) # "http://google.com:1337/test#cats?dogs_allowed=ño"
3640
end
3741

3842
defp form_scheme(""), do: ""
@@ -44,6 +48,11 @@ defmodule Norma.Utils do
4448
defp form_query(nil), do: ""
4549
defp form_query(query), do: "?" <> query
4650

51+
defp form_port(""), do: ""
52+
defp form_port(port)
53+
when port in @masked_ports, do: ""
54+
defp form_port(port), do: ":" <> port
55+
4756
defp safe_concat(left, right) do
4857
left = left || ""
4958
right = right || ""

mix.exs

+2-2
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ defmodule Norma.Mixfile do
44
def project do
55
[
66
app: :norma,
7-
version: "1.2.0",
8-
elixir: "~> 1.2",
7+
version: "1.4.2",
8+
elixir: "~> 1.3",
99
start_permanent: Mix.env == :prod,
1010
description: description(),
1111
package: package(),

test/norma_test.exs

+15-4
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,23 @@
11
defmodule NormaTest do
22
use ExUnit.Case
3+
34
@with_scheme "https://mazing.studio"
45
@without_scheme "mazing.studio"
5-
@without_scheme_but_port "//mazing.studio:21"
6+
@without_scheme_but_path "mazing.studio/test"
7+
@without_scheme_but_port "mazing.studio:21"
8+
@without_scheme_but_port_alt "mazing.studio:1337"
9+
@without_scheme_but_port_and_path "mazing.studio:1337/test"
610
@with_path "https://mazing.studio/test"
711
@with_fragment "https://mazing.studio#test"
812
@with_www "https://www.mazing.studio"
913
@full_example "//www.mazing.studio:1337/test#test"
1014

11-
test "scheme defaults to `http` when not provided (nor a port)",
12-
do: assert Norma.normalize(@without_scheme) == "http://mazing.studio"
15+
test "scheme defaults to `http` when not provided (nor a port)" do
16+
assert Norma.normalize(@without_scheme) == "http://mazing.studio"
17+
assert Norma.normalize(@without_scheme_but_path) == "http://mazing.studio/test"
18+
assert Norma.normalize(@without_scheme_but_port_alt) == "http://mazing.studio:1337"
19+
assert Norma.normalize(@without_scheme_but_port_and_path) == "http://mazing.studio:1337/test"
20+
end
1321

1422
test "scheme gets infered from port",
1523
do: assert Norma.normalize(@without_scheme_but_port) == "ftp://mazing.studio"
@@ -34,6 +42,9 @@ defmodule NormaTest do
3442
do: assert Norma.normalize(@full_example,
3543
%{force_root_path: true,
3644
remove_fragment: true,
37-
remove_www: true}) == "http://mazing.studio/"
45+
remove_www: true}) == "http://mazing.studio:1337/"
46+
47+
test "identify invalid URL",
48+
do: assert Norma.normalize_if_valid("mazing") == {:error, "Not an URL."}
3849

3950
end

0 commit comments

Comments
 (0)