@@ -12,7 +12,7 @@ defmodule URI do
1212 """
1313
1414 defstruct scheme: nil ,
15- path: nil ,
15+ path: "" ,
1616 query: nil ,
1717 fragment: nil ,
1818 authority: nil ,
@@ -21,16 +21,27 @@ defmodule URI do
2121 port: nil
2222
2323 @ type t :: % __MODULE__ {
24- scheme: nil | binary ,
25- path: nil | binary ,
26- query: nil | binary ,
24+ authority: authority ,
2725 fragment: nil | binary ,
28- authority: nil | binary ,
29- userinfo: nil | binary ,
3026 host: nil | binary ,
31- port: nil | :inet . port_number ( )
27+ path: binary ,
28+ port: nil | :inet . port_number ( ) ,
29+ query: nil | binary ,
30+ scheme: nil | binary ,
31+ userinfo: nil | binary
3232 }
3333
34+ @ typedoc deprecated: "The authority field is deprecated"
35+ @ opaque authority :: nil | binary
36+
37+ defmodule Error do
38+ defexception [ :action , :reason , :part ]
39+
40+ def message ( % Error { action: action , reason: reason , part: part } ) do
41+ "cannot #{ action } due to reason #{ reason } : #{ inspect ( part ) } "
42+ end
43+ end
44+
3445 import Bitwise
3546
3647 @ reserved_characters ':/?#[]@!$&\' ()*+,;='
@@ -465,89 +476,202 @@ defmodule URI do
465476 defp hex_to_dec ( _n ) , do: nil
466477
467478 @ doc """
468- Parses a well-formed URI into its components.
479+ Creates a new URI struct from a URI or a string.
480+
481+ If a `%URI{}` struct is given, it returns `{:ok, uri}`. If a string is
482+ given, it will parse it and returns `{:ok, uri}`. If the string is
483+ invalid, it returns `{:error, part}` instead, with the invalid part of the URI.
469484
470485 This function can parse both absolute and relative URLs. You can check
471486 if a URI is absolute or relative by checking if the `scheme` field is
472- nil or not. Furthermore, this function expects both absolute and
473- relative URIs to be well-formed and does not perform any validation.
474- See the "Examples" section below.
475-
476- When a URI is given without a port, the value returned by
477- `URI.default_port/1` for the URI's scheme is used for the `:port` field.
487+ `nil` or not. All fields may be `nil`, except for the `path`.
478488
479- When a URI hostname is an IPv6 literal, it has the `[]` unwrapped before
480- being stored in the `:host` field. Note this doesn't match the formal
481- grammar for hostnames, which preserves the `[]` around the IP. You can
482- parse the IP address by calling `:inet.parse_address/1` (remember to
483- call `String.to_charlist/1` to convert the host to a charlist before
484- calling `:inet`).
485-
486- If a `%URI{}` struct is given to this function, this function returns it
487- unmodified.
489+ When a URI is given without a port, the value returned by `URI.default_port/1`
490+ for the URI's scheme is used for the `:port` field. The scheme is also
491+ normalized to lowercase.
488492
489493 ## Examples
490494
491- iex> URI.parse("https://elixir-lang.org/")
492- %URI{
493- authority: "elixir-lang.org",
495+ iex> URI.new("https://elixir-lang.org/")
496+ {:ok, %URI{
494497 fragment: nil,
495498 host: "elixir-lang.org",
496499 path: "/",
497500 port: 443,
498501 query: nil,
499502 scheme: "https",
500503 userinfo: nil
501- }
504+ }}
502505
503- iex> URI.parse("//elixir-lang.org/")
504- %URI{
505- authority: "elixir-lang.org",
506+ iex> URI.new("//elixir-lang.org/")
507+ {:ok, %URI{
506508 fragment: nil,
507509 host: "elixir-lang.org",
508510 path: "/",
509511 port: nil,
510512 query: nil,
511513 scheme: nil,
512514 userinfo: nil
513- }
515+ }}
514516
515- iex> URI.parse("/foo/bar")
516- %URI{
517- authority: nil,
517+ iex> URI.new("/foo/bar")
518+ {:ok, %URI{
518519 fragment: nil,
519520 host: nil,
520521 path: "/foo/bar",
521522 port: nil,
522523 query: nil,
523524 scheme: nil,
524525 userinfo: nil
525- }
526+ }}
526527
527- iex> URI.parse("foo/bar")
528- %URI{
529- authority: nil,
528+ iex> URI.new("foo/bar")
529+ {:ok, %URI{
530530 fragment: nil,
531531 host: nil,
532532 path: "foo/bar",
533533 port: nil,
534534 query: nil,
535535 scheme: nil,
536536 userinfo: nil
537- }
537+ }}
538538
539- iex> URI.parse("//[fe80::]/")
540- %URI{
541- authority: "[fe80::]",
539+ iex> URI.new("//[fe80::]/")
540+ {:ok, %URI{
542541 fragment: nil,
543542 host: "fe80::",
544543 path: "/",
545544 port: nil,
546545 query: nil,
547546 scheme: nil,
548547 userinfo: nil
548+ }}
549+
550+ iex> URI.new("https:?query")
551+ {:ok, %URI{
552+ fragment: nil,
553+ host: nil,
554+ path: "",
555+ port: 443,
556+ query: "query",
557+ scheme: "https",
558+ userinfo: nil
559+ }}
560+
561+ iex> URI.new("/invalid_greater_than_in_path/>")
562+ {:error, ">"}
563+
564+ Giving an existing URI simply returns it wrapped in a tuple:
565+
566+ iex> {:ok, uri} = URI.new("https://elixir-lang.org/")
567+ iex> URI.new(uri)
568+ {:ok, %URI{
569+ fragment: nil,
570+ host: "elixir-lang.org",
571+ path: "/",
572+ port: 443,
573+ query: nil,
574+ scheme: "https",
575+ userinfo: nil
576+ }}
577+ """
578+ @ doc since: "1.13.0"
579+ @ spec new ( t ( ) | String . t ( ) ) :: { :ok , t ( ) } | { :error , String . t ( ) }
580+ def new ( % URI { } = uri ) , do: { :ok , uri }
581+
582+ def new ( binary ) when is_binary ( binary ) do
583+ case :uri_string . parse ( binary ) do
584+ % { } = map -> { :ok , uri_from_map ( map ) }
585+ { :error , :invalid_uri , term } -> { :error , Kernel . to_string ( term ) }
586+ end
587+ end
588+
589+ @ doc """
590+ Similar to `new/0` but raises `URI.Error` if an invalid string is given.
591+
592+ ## Examples
593+
594+ iex> URI.new!("https://elixir-lang.org/")
595+ %URI{
596+ fragment: nil,
597+ host: "elixir-lang.org",
598+ path: "/",
599+ port: 443,
600+ query: nil,
601+ scheme: "https",
602+ userinfo: nil
603+ }
604+
605+ iex> URI.new!("/invalid_greater_than_in_path/>")
606+ ** (URI.Error) cannot parse due to reason invalid_uri: ">"
607+
608+ Giving an existing URI simply returns it:
609+
610+ iex> uri = URI.new!("https://elixir-lang.org/")
611+ iex> URI.new!(uri)
612+ %URI{
613+ fragment: nil,
614+ host: "elixir-lang.org",
615+ path: "/",
616+ port: 443,
617+ query: nil,
618+ scheme: "https",
619+ userinfo: nil
549620 }
550621 """
622+ @ doc since: "1.13.0"
623+ @ spec new! ( t ( ) | String . t ( ) ) :: t ( )
624+ def new! ( % URI { } = uri ) , do: uri
625+
626+ def new! ( binary ) when is_binary ( binary ) do
627+ case :uri_string . parse ( binary ) do
628+ % { } = map ->
629+ uri_from_map ( map )
630+
631+ { :error , reason , part } ->
632+ raise Error , action: :parse , reason: reason , part: Kernel . to_string ( part )
633+ end
634+ end
635+
636+ defp uri_from_map ( map ) do
637+ uri = Map . merge ( % URI { } , map )
638+
639+ case map do
640+ % { scheme: scheme } ->
641+ scheme = String . downcase ( scheme , :ascii )
642+
643+ case map do
644+ % { port: _ } ->
645+ % { uri | scheme: scheme }
646+
647+ % { } ->
648+ case default_port ( scheme ) do
649+ nil -> % { uri | scheme: scheme }
650+ port -> % { uri | scheme: scheme , port: port }
651+ end
652+ end
653+
654+ % { } ->
655+ uri
656+ end
657+ end
658+
659+ @ doc """
660+ Parses a well-formed URI into its components.
661+
662+ This function is deprecated as it fails to raise in case of invalid URIs.
663+ Use `URI.new!/1` or `URI.new/1` instead. In case you want to mimic the
664+ behaviour of this function, you can do:
665+
666+ case URI.new(path) do
667+ {:ok, uri} -> uri
668+ {:error, _, _} -> %URI{path: path}
669+ end
670+
671+ Also note this function sets the authority field, but the field has been
672+ deprecated and it is not set by `URI.new!/1` and `URI.new/1`.
673+ """
674+ @ doc deprecated: "Use URI.new/1 or URI.new!/1 instead"
551675 @ spec parse ( t | binary ) :: t
552676 def parse ( % URI { } = uri ) , do: uri
553677
@@ -582,7 +706,6 @@ defmodule URI do
582706 parts
583707
584708 scheme = nillify ( scheme )
585- path = nillify ( path )
586709 query = nillify_query ( query_with_question_mark )
587710 { authority , userinfo , host , port } = split_authority ( authority_with_slashes )
588711
@@ -646,24 +769,6 @@ defmodule URI do
646769 iex> URI.to_string(uri)
647770 "foo://bar.baz"
648771
649- Note that when creating this string representation, the `:authority` value will be
650- used if the `:host` is `nil`. Otherwise, the `:userinfo`, `:host`, and `:port` will
651- be used.
652-
653- iex> URI.to_string(%URI{authority: "foo@example.com:80"})
654- "//foo@example.com:80"
655-
656- iex> URI.to_string(%URI{userinfo: "bar", host: "example.org", port: 81})
657- "//bar@example.org:81"
658-
659- iex> URI.to_string(%URI{
660- ...> authority: "foo@example.com:80",
661- ...> userinfo: "bar",
662- ...> host: "example.org",
663- ...> port: 81
664- ...> })
665- "//bar@example.org:81"
666-
667772 """
668773 @ spec to_string ( t ) :: binary
669774 defdelegate to_string ( uri ) , to: String.Chars.URI
@@ -711,7 +816,7 @@ defmodule URI do
711816 merge ( parse ( base ) , parse ( rel ) )
712817 end
713818
714- defp merge_paths ( nil , rel_path ) , do: merge_paths ( "/" , rel_path )
819+ defp merge_paths ( "" , rel_path ) , do: merge_paths ( "/" , rel_path )
715820 defp merge_paths ( _ , "/" <> _ = rel_path ) , do: remove_dot_segments_from_path ( rel_path )
716821
717822 defp merge_paths ( base_path , rel_path ) do
@@ -750,12 +855,10 @@ defmodule URI do
750855end
751856
752857defimpl String.Chars , for: URI do
753- def to_string ( % { host: host , authority: authority , path: path } = uri )
754- when ( host != nil or authority != nil ) and is_binary ( path ) and
755- path != "" and binary_part ( path , 0 , 1 ) != "/" do
858+ def to_string ( % { host: host , path: path } = uri )
859+ when host != nil and path != "" and binary_part ( path , 0 , 1 ) != "/" do
756860 raise ArgumentError ,
757- ":path in URI must be nil or an absolute path if :host or :authority are given, " <>
758- "got: #{ inspect ( uri ) } "
861+ ":path in URI must be empty or an absolute path if URL has a :host, got: #{ inspect ( uri ) } "
759862 end
760863
761864 def to_string ( % { scheme: scheme , port: port , path: path , query: query , fragment: fragment } = uri ) do
0 commit comments