Url.Parser.Query

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 for parsing the query part.

In this library, a valid query looks like ?search=hats&page=2 where each query parameter has the format key=value and is separated from the next parameter by the & character.

Parse Query Parameters

type alias Parser a = QueryParser a

Parse a query like ?search=hat&page=2 into nice Gren data.

string : String -> Parser (Maybe String)

Handle String parameters.

search : Parser (Maybe String)
search =
  string "search"

-- ?search=cats             == Just "cats"
-- ?search=42               == Just "42"
-- ?branch=left             == Nothing
-- ?search=cats&search=dogs == Nothing

Check out custom if you need to handle multiple search parameters for some reason.

int : String -> Parser (Maybe Int)

Handle Int parameters. Maybe you want to show paginated search results:

page : Parser (Maybe Int)
page =
  int "page"

-- ?page=2        == Just 2
-- ?page=17       == Just 17
-- ?page=two      == Nothing
-- ?sort=date     == Nothing
-- ?page=2&page=3 == Nothing

Check out custom if you need to handle multiple page parameters or something like that.

enum : String -> Dict String a -> Parser (Maybe a)

Handle enumerated parameters. Maybe you want a true-or-false parameter:

import Dict

debug : Parser (Maybe Bool)
debug =
  enum "debug" (Dict.fromArray [ ("true", True), ("false", False) ])

-- ?debug=true            == Just True
-- ?debug=false           == Just False
-- ?debug=1               == Nothing
-- ?debug=0               == Nothing
-- ?true=true             == Nothing
-- ?debug=true&debug=true == Nothing

You could add 0 and 1 to the dictionary if you want to handle those as well. You can also use map to say map (Maybe.withDefault False) debug to get a parser of type Parser Bool that swallows any errors and defaults to False.

Note: Parameters like ?debug with no = are not supported by this library.

custom : String -> (Array String -> a) -> Parser a

Create a custom query parser. The string, int, and enum parsers are defined using this function. It can help you handle anything though!

Say you are unlucky enough to need to handle ?post=2&post=7 to show a couple posts on screen at once. You could say:

posts : Parser (Array Int)
posts =
  custom "post" (Array.filterMap String.toInt)

-- ?post=2        == [2]
-- ?post=2&post=7 == [2, 7]
-- ?post=2&post=x == [2]
-- ?hats=2        == []

Mapping

map : (a -> b) -> Parser a -> Parser b

Transform a parser in some way. Maybe you want your page query parser to default to 1 if there is any problem?

page : Parser Int
page =
  map (Maybe.withDefault 1) (int "page")
map2 : (a -> b -> result) -> Parser a -> Parser b -> Parser result

Combine two parsers. A query like ?search=hats&page=2 could be parsed with something like this:

type alias Query =
  { search : Maybe String
  , page : Maybe Int
  }

query : Parser Query
query =
  map2 Query (string "search") (int "page")
map3 :
(a -> b -> c -> result)
-> Parser a
-> Parser b
-> Parser c
-> Parser result

Combine three parsers. A query like ?search=hats&page=2&sort=ascending could be parsed with something like this:

import Dict

type alias Query =
  { search : Maybe String
  , page : Maybe Int
  , sort : Maybe Order
  }

type Order = Ascending | Descending

query : Parser Query
query =
  map3 Query (string "search") (int "page") (enum "sort" order)

order : Dict.Dict String Order
order =
  Dict.fromArray
    [ ( "ascending", Ascending )
    , ( "descending", Descending )
    ]
map4 :
(a -> b -> c -> d -> result)
-> Parser a
-> Parser b
-> Parser c
-> Parser d
-> Parser result
map5 :
(a -> b -> c -> d -> e -> result)
-> Parser a
-> Parser b
-> Parser c
-> Parser d
-> Parser e
-> Parser result
map6 :
(a -> b -> c -> d -> e -> f -> result)
-> Parser a
-> Parser b
-> Parser c
-> Parser d
-> Parser e
-> Parser f
-> Parser result
map7 :
(a -> b -> c -> d -> e -> f -> g -> result)
-> Parser a
-> Parser b
-> Parser c
-> Parser d
-> Parser e
-> Parser f
-> Parser g
-> Parser result
map8 :
(a -> b -> c -> d -> e -> f -> g -> h -> result)
-> Parser a
-> Parser b
-> Parser c
-> Parser d
-> Parser e
-> Parser f
-> Parser g
-> Parser h
-> Parser result

If you need higher than eight, you can define a function like this:

apply : Parser a -> Parser (a -> b) -> Parser b
apply argParser funcParser =
  map2 (<|) funcParser argParser

And then you can chain it to do as many of these as you would like:

map func (string "search")
  |> apply (int "page")
  |> apply (int "per-page")