APIUrl

Url

URLs: parsing, printing, resolution, and percent-encoding, following RFC 3986.

A Url is a record of its components, so it is read and updated with ordinary field access. Internationalized host names are not supported: hosts must be ASCII.

Component

type Component    = PathSegment    | QueryPart    | FragmentPartSource

The part of a URL a string is encoded for, which decides what stays literal.

Constructors

PathSegment

PathSegment : ComponentSource

One path segment: unreserved characters, sub-delimiters, :, and @ stay literal, and / is encoded.

QueryPart

QueryPart : ComponentSource

One query key or value: unreserved characters and ! $ ' ( ) * , / : ; ? @ stay literal; &, =, +, #, and spaces are encoded.

FragmentPart

FragmentPart : ComponentSource

A fragment: path characters, /, and ? stay literal.

Instances:

Eq Component
Show Component

Url

type Url =    { scheme : Maybe String    , userinfo : Maybe String    , host : Maybe String    , port : Maybe Int    , path : String    , query : Maybe String    , fragment : Maybe String    }Source

A URI reference, absolute or relative. A relative one has no scheme and possibly no host. path, query, and fragment hold their encoded form.

Fields

scheme

scheme : Maybe StringSource

The scheme, lowercased and without its :, as in Just "https".

userinfo

userinfo : Maybe StringSource

The user information before @ in the authority. It occurs only with a host.

host

host : Maybe StringSource

The host, present exactly when the reference has an authority (//). A registered name is lowercased, and an IPv6 literal is held without its brackets, as a socket dial needs it.

port

port : Maybe IntSource

The port, when the authority names one. It occurs only with a host.

path

path : StringSource

The encoded path, possibly empty.

query

query : Maybe StringSource

The encoded query, without its ?. Just "" is a bare ?, which stays distinct from Nothing.

fragment

fragment : Maybe StringSource

The encoded fragment, without its #.

Instances:

Eq Url
Show Url

appendPath

appendPath : List String -> Url -> UrlSource

Appends segments to the path, encoding each one, / included.

import Url

joined : String -> Maybe String
joined text =
    case Url.parse text of
        Just url -> Just (Url.toString (Url.appendPath ["a b", "c/d"] url))
        Nothing -> Nothing

joined "http://h/base/" == Just "http://h/base/a%20b/c%2Fd"

parse

parse : String -> Maybe UrlSource

Parses a URI reference, or answers Nothing for a character its component does not allow (such as a space or a non-ASCII character; encode it first), a malformed percent escape, a relative reference whose first path segment holds a :, a malformed or non-ASCII host, and a port that is not decimal or is above 65535.

import Url

hostOf : String -> Maybe String
hostOf text =
    case Url.parse text of
        Just url -> Just (Maybe.withDefault "" url.host)
        Nothing -> Nothing

hostOf "HTTPS://Example.com:8080/a?b#c" == Just "example.com"
hostOf "http://[::1]/" == Just "::1"
hostOf "/docs" == Just ""
Url.parse "a b" == Nothing
Url.parse "http://h:70000/" == Nothing

percentDecode

percentDecode : String -> Maybe StringSource

Decodes percent escapes, or answers Nothing for a malformed escape or bytes that are not valid UTF-8. + stays as it is.

import Url

Url.percentDecode "a%20b+c" == Just "a b+c"
Url.percentDecode "%zz" == Nothing
Url.percentDecode "%FF" == Nothing

percentEncode

percentEncode : Component -> String -> StringSource

Percent-encodes the UTF-8 bytes of raw that component does not keep literally, as %XX with uppercase hex.

import Url exposing (Component(..))

Url.percentEncode PathSegment "a b/c" == "a%20b%2Fc"
Url.percentEncode QueryPart "x=1&y" == "x%3D1%26y"
Url.percentEncode PathSegment "é" == "%C3%A9"

queryParams

queryParams : Url -> List (String, String)Source

The decoded key=value pairs of the query, in order. + reads as a space, as HTML forms write it; empty parts are skipped, a part without = has an empty value, and a part that fails to decode is kept as written.

import Url

params : String -> List (String, String)
params text =
    case Url.parse text of
        Just url -> Url.queryParams url
        Nothing -> []

params "/s?q=a+b&&flag&x=%41" == [("q", "a b"), ("flag", ""), ("x", "A")]
params "/s" == []

requestTarget

requestTarget : Url -> StringSource

The target an HTTP request line names: the path, or / when it is empty, followed by ? and the query when there is one. The fragment is never part of it.

import Url

target : String -> Maybe String
target text =
    case Url.parse text of
        Just url -> Just (Url.requestTarget url)
        Nothing -> Nothing

target "http://h/a/b?x=1#top" == Just "/a/b?x=1"
target "http://h" == Just "/"

resolve

resolve : Url -> Url -> UrlSource

Resolves reference against base (RFC 3986 §5.2): a reference with a scheme or host replaces the base’s, an empty path keeps the base path, a relative path joins the base path’s directory, and dot segments are removed. The reference’s fragment always wins.

import Url

resolved : String -> String -> Maybe String
resolved base reference =
    case (Url.parse base, Url.parse reference) of
        (Just b, Just r) -> Just (Url.toString (Url.resolve b r))
        _ -> Nothing

resolved "http://h/a/b/c" "../d" == Just "http://h/a/d"
resolved "http://h/a/b" "?q" == Just "http://h/a/b?q"
resolved "http://h/a" "//other/x" == Just "http://other/x"

toString

toString : Url -> StringSource

The text of a URL. For anything parse accepted it is the input, except that the scheme and host are lowercased, an empty port is dropped, and an IPv6 host regains its brackets.

import Url

roundTrip : String -> Maybe String
roundTrip text =
    case Url.parse text of
        Just url -> Just (Url.toString url)
        Nothing -> Nothing

roundTrip "HTTP://Example.com:/a?b#c" == Just "http://example.com/a?b#c"
roundTrip "http://[::1]:80/" == Just "http://[::1]:80/"

withQuery

withQuery : List (String, String) -> Url -> UrlSource

Appends key=value pairs to the query, encoding each key and value. Spaces become %20, which every server accepts.

import Url

added : String -> Maybe String
added text =
    case Url.parse text of
        Just url -> Just (Url.toString (Url.withQuery [("q", "a b"), ("n", "1")] url))
        Nothing -> Nothing

added "http://h/s?x=1" == Just "http://h/s?x=1&q=a%20b&n=1"
added "http://h/s" == Just "http://h/s?q=a%20b&n=1"