Url.Parser
In the URI spec, Tim Berners-Lee says a URL looks like this:
https://example.com:8042/over/there?name=ferret#nose
\___/ \______________/\_________/ \_________/ \__/
| | | | |
scheme authority path query fragment
This module is primarily for parsing the path
part.
Primitives
Turn URLs like /blog/42/cat-herding-techniques
into nice Gren data.
Parse a segment of the path as a String
.
-- /alice/ ==> Just "alice"
-- /bob ==> Just "bob"
-- /42/ ==> Just "42"
-- / ==> Nothing
Parse a segment of the path as an Int
.
-- /alice/ ==> Nothing
-- /bob ==> Nothing
-- /42/ ==> Just 42
-- / ==> Nothing
Parse a segment of the path if it matches a given string. It is almost
always used with </>
or oneOf
. For example:
blog : Parser (Int -> a) a
blog =
s "blog" </> int
-- /blog/42 ==> Just 42
-- /tree/42 ==> Nothing
The path segment must be an exact match!
Path
Parse a path with multiple segments.
blog : Parser (Int -> a) a
blog =
s "blog" </> int
-- /blog/35/ ==> Just 35
-- /blog/42 ==> Just 42
-- /blog/ ==> Nothing
-- /42/ ==> Nothing
search : Parser (String -> a) a
search =
s "search" </> string
-- /search/wolf/ ==> Just "wolf"
-- /search/frog ==> Just "frog"
-- /search/ ==> Nothing
-- /wolf/ ==> Nothing
Transform a path parser.
type alias Comment = { user : String, id : Int }
userAndId : Parser (String -> Int -> a) a
userAndId =
s "user" </> string </> s "comment" </> int
comment : Parser (Comment -> a) a
comment =
map Comment userAndId
-- /user/bob/comment/42 ==> Just { user = "bob", id = 42 }
-- /user/tom/comment/35 ==> Just { user = "tom", id = 35 }
-- /user/sam/ ==> Nothing
Try a bunch of different path parsers.
type Route
= Topic String
| Blog Int
| User String
| Comment String Int
route : Parser (Route -> a) a
route =
oneOf
[ map Topic (s "topic" </> string)
, map Blog (s "blog" </> int)
, map User (s "user" </> string)
, map Comment (s "user" </> string </> s "comment" </> int)
]
-- /topic/wolf ==> Just (Topic "wolf")
-- /topic/ ==> Nothing
-- /blog/42 ==> Just (Blog 42)
-- /blog/wolf ==> Nothing
-- /user/sam/ ==> Just (User "sam")
-- /user/bob/comment/42 ==> Just (Comment "bob" 42)
-- /user/tom/comment/35 ==> Just (Comment "tom" 35)
-- /user/ ==> Nothing
If there are multiple parsers that could succeed, the first one wins.
A parser that does not consume any path segments.
type Route = Overview | Post Int
blog : Parser (BlogRoute -> a) a
blog =
s "blog" </>
oneOf
[ map Overview top
, map Post (s "post" </> int)
]
-- /blog/ ==> Just Overview
-- /blog/post/42 ==> Just (Post 42)
Create a custom path segment parser. Here is how it is used to define the
int
parser:
int : Parser (Int -> a) a
int =
custom "NUMBER" String.toInt
You can use it to define something like “only CSS files” like this:
css : Parser (String -> a) a
css =
custom "CSS_FILE" <| \segment ->
if String.endsWith ".css" segment then
Just segment
else
Nothing
Query
The Url.Parser.Query
module defines its own
Parser
type. This function helps you use those
with normal parsers. For example, maybe you want to add a search feature to
your blog website:
import Url.Parser.Query as Query
type Route
= Overview (Maybe String)
| Post Int
blog : Parser (Route -> a) a
blog =
oneOf
[ map Overview (s "blog" <?> Query.string "q")
, map Post (s "blog" </> int)
]
-- /blog/ ==> Just (Overview Nothing)
-- /blog/?q=wolf ==> Just (Overview (Just "wolf"))
-- /blog/wolf ==> Nothing
-- /blog/42 ==> Just (Post 42)
-- /blog/42?q=wolf ==> Just (Post 42)
-- /blog/42/wolf ==> Nothing
The Url.Parser.Query
module defines its own
Parser
type. This function is a helper to convert
those into normal parsers.
import Url.Parser.Query as Query
-- the following expressions are both the same!
--
-- s "blog" <?> Query.string "search"
-- s "blog" </> query (Query.string "search")
This may be handy if you need query parameters but are not parsing any path segments.
Fragment
Create a parser for the URL fragment, the stuff after the #
. This can
be handy for handling links to DOM elements within a page. Pages like this one!
type alias Docs =
(String, Maybe String)
docs : Parser (Docs -> a) a
docs =
map Tuple.pair (string </> fragment identity)
-- /List/map ==> Nothing
-- /List/#map ==> Just ("List", Just "map")
-- /List#map ==> Just ("List", Just "map")
-- /List# ==> Just ("List", Just "")
-- /List ==> Just ("List", Nothing)
-- / ==> Nothing
Run Parsers
Actually run a parser! You provide some Url
that
represent a valid URL. From there parse
runs your parser on the path, query
parameters, and fragment.
import Url
import Url.Parser exposing (Parser, parse, int, map, oneOf, s, top)
type Route = Home | Blog Int | NotFound
route : Parser (Route -> a) a
route =
oneOf
[ map Home top
, map Blog (s "blog" </> int)
]
toRoute : String -> Route
toRoute string =
when Url.fromString string is
Nothing ->
NotFound
Just url ->
Maybe.withDefault NotFound (parse route url)
-- toRoute "/blog/42" == NotFound
-- toRoute "https://example.com/" == Home
-- toRoute "https://example.com/blog" == NotFound
-- toRoute "https://example.com/blog/42" == Blog 42
-- toRoute "https://example.com/blog/42/" == Blog 42
-- toRoute "https://example.com/blog/42#wolf" == Blog 42
-- toRoute "https://example.com/blog/42?q=wolf" == Blog 42
-- toRoute "https://example.com/settings" == NotFound
Functions like toRoute
are useful when creating single-page apps with
Browser.application
. I use them in init
and onNavigation
to handle
the initial URL and any changes.