{-| Module : Url.Parser.Query Description : Build URL queries. License : BSD 3 Maintainer : terezasokol@gmail.com Stability : experimental Portability : POSIX In [the URI spec](https://tools.ietf.org/html/rfc3986), 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. -} module Url.Parser.Query ( Parser, string, int, enum, custom , map, map2, map3, map4, map5, map6, map7, map8 ) where import qualified Prelude import qualified Dict import qualified Maybe import qualified List import qualified String import qualified Url.Parser.Internal as Q import Cherry.Prelude import Url (Url) -- PARSERS {-| Parse a query like `?search=hat&page=2` into nice Elm data. -} type Parser a = Q.QueryParser a -- PRIMITIVES {-| 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`](#custom) if you need to handle multiple `search` parameters for some reason. -} string :: String -> Parser (Maybe String) string key = custom key <| \stringList -> case stringList of [str] -> Just str _ -> Nothing {-| 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`](#custom) if you need to handle multiple `page` parameters or something like that. -} int :: String -> Parser (Maybe Int) int key = custom key <| \stringList -> case stringList of [str] -> String.toInt str _ -> Nothing {-| Handle enumerated parameters. Maybe you want a true-or-false parameter: import Dict debug :: Parser (Maybe Bool) debug = enum "debug" (Dict.fromList [ ("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`](#map) to say `map (Result.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. -} enum :: String -> Dict.Dict String a -> Parser (Maybe a) enum key dict = custom key <| \stringList -> case stringList of [str] -> Dict.get str dict _ -> Nothing -- CUSTOM PARSERS {-| Create a custom query parser. The [`string`](#string), [`int`](#int), and [`enum`](#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 (Maybe (List Int)) posts = custom "post" (List.maybeMap String.toInt) -- ?post=2 == [2] -- ?post=2&post=7 == [2, 7] -- ?post=2&post=x == [2] -- ?hats=2 == [] -} custom :: String -> (List String -> a) -> Parser a custom key func = Q.Parser <| \dict -> func (Maybe.withDefault [] (Dict.get key dict)) -- MAPPING {-| 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 (Result.withDefault 1) (int "page") -} map :: (a -> b) -> Parser a -> Parser b map func (Q.Parser a) = Q.Parser <| \dict -> func (a dict) {-| 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") -} map2 :: (a -> b -> result) -> Parser a -> Parser b -> Parser result map2 func (Q.Parser a) (Q.Parser b) = Q.Parser <| \dict -> func (a dict) (b dict) {-| 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.fromList [ ( "ascending", Ascending ) , ( "descending", Descending ) ] -} map3 :: (a -> b -> c -> result) -> Parser a -> Parser b -> Parser c -> Parser result map3 func (Q.Parser a) (Q.Parser b) (Q.Parser c) = Q.Parser <| \dict -> func (a dict) (b dict) (c dict) {-|-} map4 :: (a -> b -> c -> d -> result) -> Parser a -> Parser b -> Parser c -> Parser d -> Parser result map4 func (Q.Parser a) (Q.Parser b) (Q.Parser c) (Q.Parser d) = Q.Parser <| \dict -> func (a dict) (b dict) (c dict) (d dict) {-|-} map5 :: (a -> b -> c -> d -> e -> result) -> Parser a -> Parser b -> Parser c -> Parser d -> Parser e -> Parser result map5 func (Q.Parser a) (Q.Parser b) (Q.Parser c) (Q.Parser d) (Q.Parser e) = Q.Parser <| \dict -> func (a dict) (b dict) (c dict) (d dict) (e dict) {-|-} map6 :: (a -> b -> c -> d -> e -> f -> result) -> Parser a -> Parser b -> Parser c -> Parser d -> Parser e -> Parser f -> Parser result map6 func (Q.Parser a) (Q.Parser b) (Q.Parser c) (Q.Parser d) (Q.Parser e) (Q.Parser f) = Q.Parser <| \dict -> func (a dict) (b dict) (c dict) (d dict) (e dict) (f dict) {-|-} 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 map7 func (Q.Parser a) (Q.Parser b) (Q.Parser c) (Q.Parser d) (Q.Parser e) (Q.Parser f) (Q.Parser g) = Q.Parser <| \dict -> func (a dict) (b dict) (c dict) (d dict) (e dict) (f dict) (g dict) {-| 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") -} 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 map8 func (Q.Parser a) (Q.Parser b) (Q.Parser c) (Q.Parser d) (Q.Parser e) (Q.Parser f) (Q.Parser g) (Q.Parser h) = Q.Parser <| \dict -> func (a dict) (b dict) (c dict) (d dict) (e dict) (f dict) (g dict) (h dict)