Skip to content

Commit a0b77bd

Browse files
committed
Do not deprecate URI.parse/1
Closes #11450.
1 parent cbba61a commit a0b77bd

File tree

2 files changed

+108
-29
lines changed

2 files changed

+108
-29
lines changed

lib/elixir/lib/uri.ex

Lines changed: 104 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,14 @@ defmodule URI do
1212
"""
1313

1414
defstruct scheme: nil,
15-
path: "",
15+
path: nil,
1616
query: nil,
1717
fragment: nil,
1818
authority: nil,
1919
userinfo: nil,
2020
host: nil,
2121
port: nil
2222

23-
# TODO: Remove nil from path when we fully deprecate URI.parse on Elixir v1.17
2423
@type t :: %__MODULE__{
2524
authority: authority,
2625
fragment: nil | binary,
@@ -480,12 +479,13 @@ defmodule URI do
480479
Creates a new URI struct from a URI or a string.
481480
482481
If a `%URI{}` struct is given, it returns `{:ok, uri}`. If a string is
483-
given, it will parse it and returns `{:ok, uri}`. If the string is
484-
invalid, it returns `{:error, part}` instead, with the invalid part of the URI.
482+
given, it will parse and validate it. If the string is valid, it returns
483+
`{:ok, uri}`, otherwise it returns `{:error, part}` with the invalid part
484+
of the URI. For parsing URIs without further validation, see `parse/1`.
485485
486486
This function can parse both absolute and relative URLs. You can check
487487
if a URI is absolute or relative by checking if the `scheme` field is
488-
`nil` or not. All fields may be `nil`, except for the `path`.
488+
`nil` or not.
489489
490490
When a URI is given without a port, the value returned by `URI.default_port/1`
491491
for the URI's scheme is used for the `:port` field. The scheme is also
@@ -552,7 +552,7 @@ defmodule URI do
552552
{:ok, %URI{
553553
fragment: nil,
554554
host: nil,
555-
path: "",
555+
path: nil,
556556
port: 443,
557557
query: "query",
558558
scheme: "https",
@@ -634,6 +634,8 @@ defmodule URI do
634634
end
635635
end
636636

637+
defp uri_from_map(%{path: ""} = map), do: uri_from_map(%{map | path: nil})
638+
637639
defp uri_from_map(map) do
638640
uri = Map.merge(%URI{}, map)
639641

@@ -658,29 +660,106 @@ defmodule URI do
658660
end
659661

660662
@doc """
661-
Parses a well-formed URI into its components.
663+
Parses a URI into its components, without further validation.
662664
663-
This function is deprecated as it fails to raise in case of invalid URIs.
664-
Use `URI.new!/1` or `URI.new/1` instead. In case you want to mimic the
665-
behaviour of this function, you can do:
665+
This function can parse both absolute and relative URLs. You can check
666+
if a URI is absolute or relative by checking if the `scheme` field is
667+
nil or not. Furthermore, this function expects both absolute and
668+
relative URIs to be well-formed and does not perform any validation.
669+
See the "Examples" section below. Use `new/1` if you want more strict
670+
validation.
666671
667-
case URI.new(path) do
668-
{:ok, uri} -> uri
669-
{:error, _} -> %URI{path: path}
670-
end
672+
When a URI is given without a port, the value returned by `URI.default_port/1`
673+
for the URI's scheme is used for the `:port` field. The scheme is also
674+
normalized to lowercase.
675+
676+
If a `%URI{}` struct is given to this function, this function returns it
677+
unmodified.
678+
679+
> Note: this function sets the field :authority for backwards
680+
> compatibility reasons but it is deprecated.
681+
682+
## Examples
683+
684+
iex> URI.parse("https://elixir-lang.org/")
685+
%URI{
686+
authority: "elixir-lang.org",
687+
fragment: nil,
688+
host: "elixir-lang.org",
689+
path: "/",
690+
port: 443,
691+
query: nil,
692+
scheme: "https",
693+
userinfo: nil
694+
}
695+
696+
iex> URI.parse("//elixir-lang.org/")
697+
%URI{
698+
authority: "elixir-lang.org",
699+
fragment: nil,
700+
host: "elixir-lang.org",
701+
path: "/",
702+
port: nil,
703+
query: nil,
704+
scheme: nil,
705+
userinfo: nil
706+
}
707+
708+
iex> URI.parse("/foo/bar")
709+
%URI{
710+
authority: nil,
711+
fragment: nil,
712+
host: nil,
713+
path: "/foo/bar",
714+
port: nil,
715+
query: nil,
716+
scheme: nil,
717+
userinfo: nil
718+
}
719+
720+
iex> URI.parse("foo/bar")
721+
%URI{
722+
authority: nil,
723+
fragment: nil,
724+
host: nil,
725+
path: "foo/bar",
726+
port: nil,
727+
query: nil,
728+
scheme: nil,
729+
userinfo: nil
730+
}
671731
672-
There are two differencws in the behaviour of this function compared to
673-
`URI.new/1`:
732+
In contrast to `URI.new/1`, this function will parse poorly-formed
733+
URIs, for example:
674734
675-
* This function sets the deprecated authority field
735+
iex> URI.parse("/invalid_greater_than_in_path/>")
736+
%URI{
737+
authority: nil,
738+
fragment: nil,
739+
host: nil,
740+
path: "/invalid_greater_than_in_path/>",
741+
port: nil,
742+
query: nil,
743+
scheme: nil,
744+
userinfo: nil
745+
}
746+
747+
Another example is a URI with brackets in query strings. It is accepted
748+
by `parse/1` but it will be refused by `new/1`:
676749
677-
* This function sets the path to `nil` when it is empty,
678-
while `new/1` consider the path always exists and sets it
679-
to an empty string
750+
iex> URI.parse("/?foo[bar]=baz")
751+
%URI{
752+
authority: nil,
753+
fragment: nil,
754+
host: nil,
755+
path: "/",
756+
port: nil,
757+
query: "foo[bar]=baz",
758+
scheme: nil,
759+
userinfo: nil
760+
}
680761
681762
"""
682-
# TODO: Deprecate me at least on v1.17
683-
@doc deprecated: "Use URI.new/1 or URI.new!/1 instead"
684763
@spec parse(t | binary) :: t
685764
def parse(%URI{} = uri), do: uri
686765

@@ -813,6 +892,7 @@ defmodule URI do
813892
%{rel | scheme: base.scheme, path: remove_dot_segments_from_path(rel.path)}
814893
end
815894

895+
# TODO: Check only for nils in future versions
816896
def merge(%URI{} = base, %URI{path: rel_path} = rel) when rel_path in ["", nil] do
817897
%{base | query: rel.query || base.query, fragment: rel.fragment}
818898
end
@@ -826,9 +906,7 @@ defmodule URI do
826906
merge(parse(base), parse(rel))
827907
end
828908

829-
# TODO: Deprecate me on Elixir v1.19
830909
defp merge_paths(nil, rel_path), do: merge_paths("/", rel_path)
831-
defp merge_paths("", rel_path), do: merge_paths("/", rel_path)
832910
defp merge_paths(_, "/" <> _ = rel_path), do: remove_dot_segments_from_path(rel_path)
833911

834912
defp merge_paths(base_path, rel_path) do
@@ -868,7 +946,8 @@ end
868946

869947
defimpl String.Chars, for: URI do
870948
def to_string(%{host: host, path: path} = uri)
871-
when host != nil and path != "" and binary_part(path, 0, 1) != "/" do
949+
when host != nil and is_binary(path) and
950+
path != "" and binary_part(path, 0, 1) != "/" do
872951
raise ArgumentError,
873952
":path in URI must be empty or an absolute path if URL has a :host, got: #{inspect(uri)}"
874953
end

lib/elixir/test/elixir/uri_test.exs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ defmodule URITest do
9494

9595
describe "new/1" do
9696
test "empty" do
97-
assert URI.new("") == {:ok, %URI{path: ""}}
97+
assert URI.new("") == {:ok, %URI{}}
9898
end
9999

100100
test "errors on bad URIs" do
@@ -131,7 +131,7 @@ defmodule URITest do
131131
query: nil,
132132
fragment: nil,
133133
port: 443,
134-
path: "",
134+
path: nil,
135135
userinfo: nil
136136
}
137137

@@ -349,9 +349,9 @@ defmodule URITest do
349349
assert URI.merge(base, "") |> to_string == "http://example.com/foo/bar"
350350
assert URI.merge(base, "#fragment") |> to_string == "http://example.com/foo/bar#fragment"
351351
assert URI.merge(base, "?query") |> to_string == "http://example.com/foo/bar?query"
352-
assert URI.merge(base, %URI{path: ""}) |> to_string == "http://example.com/foo/bar"
352+
assert URI.merge(base, %URI{}) |> to_string == "http://example.com/foo/bar"
353353

354-
assert URI.merge(base, %URI{path: "", fragment: "fragment"})
354+
assert URI.merge(base, %URI{fragment: "fragment"})
355355
|> to_string == "http://example.com/foo/bar#fragment"
356356

357357
base = URI.new!("http://example.com")

0 commit comments

Comments
 (0)