Module

Routing.Duplex

#RouteDuplex

data RouteDuplex i o

The core abstraction of this library. The values of this type can be used both for parsing values of type o from String as well as printing values of type i into String.

For most purposes, you'll likely want RouterDuplex' which uses the same type for both parameters.

Constructors

Instances

#RouteDuplex'

type RouteDuplex' a = RouteDuplex a a

A type restricted variant of RouteDuplex where input and output are the same type. This type will typically be your custom Route data type representing valid routes within your application.

#parse

parse :: forall i o. RouteDuplex i o -> String -> Either RouteError o

Uses a given codec to parse a value of type o out of String representing the path, query and fragment (hash) of a URI (see URI - generic syntax) or produce a RouteError if parsing fails.

#print

print :: forall i o. RouteDuplex i o -> i -> String

Renders a value of type i into a String representation of URI path, query and fragment (hash).

#prefix

prefix :: forall a b. String -> RouteDuplex a b -> RouteDuplex a b

Strips (when parsing) or adds (when printing) a given string segment of the path. Note: this combinator only deals with a single segment. If you pass it a string containing '/' it will percent encode it and treat it as single segment. E.g. prefix "/api/v1" will attempt to match single segment "%2Fapi%2Fv1" which is probably not what you want. See path if you want to deal with prefixes consisting of multiple segments.

parse (prefix "api" segment) "api/a" == Right "a"

parse (prefix "/api/v1" segment)) "/api/v1/a" == Left (Expected "/api/v1" "")

-- contrast with `path`
parse (path "/api/v1" segment)) "/api/v1/a" == Right "a"

#suffix

suffix :: forall a b. RouteDuplex a b -> String -> RouteDuplex a b

Similar to prefix. Strips (when parsing) or adds (when printing) a given string segment from the end of the path. The same precautions for prefix apply here.

#path

path :: forall a b. String -> RouteDuplex a b -> RouteDuplex a b

Strips (when parsing) or adds (when printing) a given String prefix, potentially consisting of multiple path segments. Constrast this with prefix, which only deals with single segment.

parse (path "/api/v1" segment) "/api/v1/a" == Right "a"
parse (path "/api/v1" segment) "/api/v2/a" == Left (Expected "v1" "v2")

#root

root :: forall a b. RouteDuplex a b -> RouteDuplex a b

Modifies a given codec to require a prefix of '/'. You can think of it as stripping and adding the '/' at the beginning of path, failing if it's not there.

parse (root segment) "/abc" == Right "abc"
parse (root segment) "abc" == Left (Expected "" "abc")

print (root segment) "abc" == "/abc"

#end

end :: forall a b. RouteDuplex a b -> RouteDuplex a b

end codec will only suceed if codec succeeds and there are no additional path segments remaining to be processed.

parse (end segment) "abc" == Right "abc"
parse (end segment) "abc/def" == Left (ExpectedEndOfPath "def")

#segment

segment :: RouteDuplex' String

Consumes or prints a single path segment. Note: URI encoding and decoding is done automatically.

parse segment "abc"         == Right "abc"
parse segment "abc%20def"   == Right "abc def" -- automatic decoding of uri components
parse segment "abc/def"     == Right "abc"
parse segment "/abc"        == Right "" -- the empty string before the first '/'
parse (root segment) "/abc" == Right "abc"

print segment "hello there" == "hello%20there"
print segment "" == "/"

#param

param :: String -> RouteDuplex' String

param name consumes or prints a query parameter with the given name. Parsing will fail if the parameter is not there.

parse (param "search") "?search=keyword" == Right "keyword"
parse (param "search") "/"               == Left (MissingParam "search")
parse (optional (param "search")) "/"    == Right Nothing

#flag

flag :: RouteDuplex' String -> RouteDuplex' Boolean

Consumes or prints a query flag (i.e. parameter without value). Note: that this combinator ignores the value of the parameter. It only cares about its presence/absence. Presence is interpreted as true, absence as false.

parse (flag (param "x")) "?x"        == Right true
parse (flag (param "x")) "?x=true",  == Right true
parse (flag (param "x")) "?x=false", == Right true -- value is ignored, what matters is presence of the parameter x
parse (flag (param "x")) "?y",       == Right false

#many1

many1 :: forall f a b. Foldable f => Alt f => Applicative f => RouteDuplex a b -> RouteDuplex (f a) (f b)

Repeatedly applies a given codec to parse one or more values from path segments. Parsing will fail if no segment can be parsed.

parse (many1 (int segment)) "1/2/3/x" == Right [1,2,3]
parse (many1 (int segment)) "x",      == Left (Expected "Int" "x") :: Either RouteError (Array Int)

#many

many :: forall f a b. Foldable f => Alternative f => RouteDuplex a b -> RouteDuplex (f a) (f b)

Similar to many1, except also succeeds when no values can be parsed.

parse (many (int segment)) "1/2/3/x" == Right [1,2,3]
parse (many (int segment)) "x",      == Right []

#rest

rest :: RouteDuplex' (Array String)

Consumes or prints all the remaining segments.

parse rest "" == Right []
parse (path "a/b" rest) "a/b/c/d" == Right ["c", "d"]

print rest ["a", "b"] == "a/b"

#default

default :: forall a b. b -> RouteDuplex a b -> RouteDuplex a b

Sets a default value which will be returned when parsing fails. Does not influence printing in any way.

parse (default 0 $ int segment) "1" == Right 1
parse (default 0 $ int segment) "x" == Right 0

#optional

optional :: forall a b. RouteDuplex a b -> RouteDuplex (Maybe a) (Maybe b)

Augments the behavior of a given codec by making it return Nothing if parsing fails, or Just value if it succeeds.

parse (optional segment) "a"        == Right (Just "a")
parse (optional segment) ""         == Right Nothing

print (optional segment) (Just "a") == "a"
print (optional segment) Nothing    == ""

#as

as :: forall s a b. (a -> s) -> (String -> Either String b) -> RouteDuplex s String -> RouteDuplex a b

Builds a codec for a custom type out of printer and parser functions.

data Sort = Asc | Desc

sortToString :: Sort -> String
sortToString = case _ of
  Asc -> "asc"
  Desc -> "desc"

sortFromString :: String -> Either String Sort
sortFromString = case _ of
  "asc" -> Right Asc
  "desc" -> Right Desc
  val -> Left $ "Not a sort: " <> val

sort :: RouteDuplex' String -> RouteDuplex' Sort
sort = as sortToString sortFromString

#int

int :: RouteDuplex' String -> RouteDuplex' Int

Refines a codec of Strings to Ints.

parse (int segment) "1"  == Right 1
parse (int segment) "x"  == Left (Expected "Int" "x")

print (int segment) 1    == "1"

#boolean

boolean :: RouteDuplex' String -> RouteDuplex' Boolean

Refines a codec of Strings to Booleans, where true and false are the strings "true" and "false", and other strings are rejected.

parse (boolean segment) "true"  == Right true
parse (boolean segment) "x"     == Left (Expected "Boolean" "x")

print (boolean segment) true    == "true"

#string

string :: RouteDuplex' String -> RouteDuplex' String

This does nothing (internally it's defined as identity). It can be used to restrict a type parameter of a polymorphic RouteDuplex' a to String.

#record

record :: forall r. RouteDuplex r (Record ())

Combined with prop or :=, builds a Record where the order of parsing and printing matters.

date =
  record
    # prop (SProxy :: _ "year") (int segment)
    # prop (SProxy :: _ "month") (int segment)
    # prop (SProxy :: _ "day") (int segment)

parse (path "blog" date) "blog/2019/1/2" ==
  Right { year: 2019, month: 1, day: 2 }

#prop

prop :: forall sym a b r1 r2 r3 rx. IsSymbol sym => Cons sym a rx r1 => Cons sym b r2 r3 => Lacks sym r2 => SProxy sym -> RouteDuplex a b -> RouteDuplex (Record r1) (Record r2) -> RouteDuplex (Record r1) (Record r3)

See record.

#(:=)

Operator alias for Routing.Duplex.prop (non-associative / precedence 2)

#RouteDuplexParams

class RouteDuplexParams :: Row Type -> Row Type -> Constraintclass RouteDuplexParams (r1 :: Row Type) (r2 :: Row Type) | r1 -> r2 where

Members

  • params :: Record r1 -> RouteDuplex' (Record r2)

    Builds a RouteDuplex from a record of query parameter parsers/printers, where each property corresponds to a query parameter with the same name.

    search =
      params
        { page: int
        , filter: optional <<< string
        }
    
    parse search "?page=3&filter=Galaxy%20Quest" ==
      Right { page: 3, filter: Just "Galaxy Quest" }
    

Instances

#RouteDuplexBuildParams

class RouteDuplexBuildParams :: RowList Type -> Row Type -> Row Type -> Row Type -> Row Type -> Constraintclass RouteDuplexBuildParams (rl :: RowList Type) (r1 :: Row Type) (r2 :: Row Type) (r3 :: Row Type) (r4 :: Row Type) | rl -> r1 r2 r3 r4 where

Members

Instances

Modules