andrewMacmurray / elm-url-query-pipeline / Url.Query.Pipeline

Combine elm/url Url Query param parsers using a pipeline style - think elm-json-decode-pipeline but for Url Query params:

import Url exposing (Url)
import Url.Parser as Parser exposing ((<?>), s, top)
import Url.Parser.Query as Query
import Url.Query.Pipeline as Pipeline

type Route
    = Home (Maybe MyQuery)
    | AnotherRoute

type alias MyQuery =
    { one : String
    , two : Maybe String
    , three : List Int
    }

query : Query.Parser (Maybe MyQuery)
query =
    Pipeline.succeed MyQuery
        |> Pipeline.required (Query.string "one")
        |> Pipeline.optional (Query.string "two")
        |> Pipeline.with (Query.custom "three" (List.filterMap String.toInt))

routes : Parser.Parser (Route -> a) a
routes =
    Parser.oneOf
        [ Parser.map Home (top <?> query)
        , Parser.map AnotherRoute (s "another-route")
        ]

fromString : String -> Maybe Route
fromString =
    Url.fromString >> Maybe.andThen (Parser.parse routes)

Some examples from above:

fromString "http://example/another-route" == Just AnotherRoute

fromString "http://example?one=hello&two=world&three=1&three=2"
    == Just
        (Home
            (Just
                { one = "hello"
                , two = Just "world"
                , three = [ 1, 2 ]
                }
            )
        )

-- required param "one" is missing
fromString "http://example?two=world&three=1"
    == Just (Home Nothing)

The examples below use the function parse

parse : Query.Parser (Maybe MyQuery) -> String -> Maybe MyQuery

Why is the output Maybe MyQuery?

elm/url's Query Parsers all return Maybe a.

The Pipeline functions is also Maybe a to maintain compatibility with elm/url.

Start a Pipeline

succeed : a -> Url.Parser.Query.Parser (Maybe a)

Start off a pipeline

type alias MyQuery =
    { one : String
    , two : Maybe String
    , three : Int
    }

myQuery : Query.Parser (Maybe MyQuery)
myQuery =
    Pipeline.succeed MyQuery
        |> Pipeline.required (Query.string "one")
        |> Pipeline.optional (Query.string "two")
        |> Pipeline.required (Query.int "three")

Examples:

parse myQuery "one=one&two=two&three=3"
    == Just
        { one = "one"
        , two = Just "two"
        , three = 3
        }

parse myQuery "one=one&three=3"
    == Just
        { one = "one"
        , two = Nothing
        , three = 3
        }

Build a Pipeline

required : Url.Parser.Query.Parser (Maybe a) -> Url.Parser.Query.Parser (Maybe (a -> b)) -> Url.Parser.Query.Parser (Maybe b)

Combine a parser that must not be Nothing, if the parser returns Nothing the whole pipeline will return Nothing

type alias MyQuery =
    { one : String
    , two : Maybe String
    }

myQuery : Query.Parser (Maybe MyQuery)
myQuery =
    Pipeline.succeed MyQuery
        |> Pipeline.required (Query.string "one")
        |> Pipeline.optional (Query.string "two")

Examples:

parse myQuery "one=one&two=two"
    == Just { one = "one", two = Just "two" }

parse myQuery "one=one"
    == Just { one = "one", two = Nothing }

-- missing required param
parse myQuery "two=two"
    == Nothing

optional : Url.Parser.Query.Parser (Maybe a) -> Url.Parser.Query.Parser (Maybe (Maybe a -> b)) -> Url.Parser.Query.Parser (Maybe b)

Combine a parser that returns a Maybe value

type alias MyQuery =
    { one : Maybe String
    , two : Maybe String
    }

myQuery : Query.Parser (Maybe MyQuery)
myQuery =
    Pipeline.succeed MyQuery
        |> Pipeline.optional (Query.string "one")
        |> Pipeline.optional (Query.string "two")

Examples:

parse myQuery "one=one"
    == Just { one = Just "one", two = Nothing }

parse myQuery "two=two"
    == Just { one = Nothing "one", two = Just "two" }

parse myQuery ""
    == Just { one = Nothing, two = Nothing }

with : Url.Parser.Query.Parser a -> Url.Parser.Query.Parser (Maybe (a -> b)) -> Url.Parser.Query.Parser (Maybe b)

Combine any parser as is: useful for Lists or Custom Types

type alias MyQuery =
    { one : List String
    , two : Fruit
    }

type Fruit
    = Apple
    | Pear

myQuery : Query.Parser (Maybe MyQuery)
myQuery =
    Pipeline.succeed MyQuery
        |> Pipeline.with (Query.custom "one" identity)
        |> Pipeline.with (Query.enum "two" fruitOptions |> Query.map (Maybe.withDefault Apple))

Examples:

parse myQuery "one=a&one=b&two=pear"
    == Just { one = [ "a", "b" ], two = Pear }

parse myQuery "two=apple"
    == Just { one = [], two = Apple }

withDefault : Url.Parser.Query.Parser (Maybe a) -> a -> Url.Parser.Query.Parser (Maybe (a -> b)) -> Url.Parser.Query.Parser (Maybe b)

Apply a default value for a parser containing a Maybe

type alias MyQuery =
    { one : String
    , two : Fruit
    }

type Fruit
    = Apple
    | Pear

myQuery : Query.Parser (Maybe MyQuery)
myQuery =
    Pipeline.succeed MyQuery
        |> Pipeline.required (Query.string "one")
        |> Pipeline.withDefault (Query.enum "two" fruitOptions) Apple

Examples:

parse myQuery "one=one&two=pear"
    == Just { one = "one", two = Pear }

parse myQuery "one=one"
    == Just { one = "one", two = Apple }

hardcoded : a -> Url.Parser.Query.Parser (Maybe (a -> b)) -> Url.Parser.Query.Parser (Maybe b)

Apply a hardcoded value

type alias MyQuery =
    { one : String
    , two : Int
    }

myQuery : Query.Parser (Maybe MyQuery)
myQuery =
    Pipeline.succeed MyQuery
        |> Pipeline.required (Query.string "one")
        |> Pipeline.hardcoded 42

Examples:

parse myQuery "one=one"
    == Just { one = "one", two = 42 }