Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/Routing/Duplex.purs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ module Routing.Duplex
, path
, root
, end
, end'
, segment
, param
, flag
Expand Down Expand Up @@ -141,6 +142,11 @@ root = path ""
end :: forall a b. RouteDuplex a b -> RouteDuplex a b
end (RouteDuplex enc dec) = RouteDuplex enc (dec <* Parser.end)

-- | Strict version of `end codec` will only suceed if `codec` succeeds and there are no
-- | additional path segments, hashes, parameters remaining to be processed.
end' :: forall a b. RouteDuplex a b -> RouteDuplex a b
end' (RouteDuplex enc dec) = RouteDuplex enc (dec <* Parser.end')

-- | Consumes or prints a single path segment.
-- | **Note:** [URI encoding and decoding](https://en.wikipedia.org/wiki/Percent-encoding) is done automatically.
-- |
Expand Down
15 changes: 14 additions & 1 deletion src/Routing/Duplex/Generic.purs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import Data.Profunctor (dimap)
import Data.Symbol (class IsSymbol)
import Prim.Row as Row
import Record as Record
import Routing.Duplex (RouteDuplex(..), RouteDuplex', end)
import Routing.Duplex (RouteDuplex(..), RouteDuplex', end, end')
import Type.Proxy (Proxy(..))

-- | Builds a parser/printer from a record, where each record field corresponds
Expand All @@ -25,6 +25,19 @@ sum
-> RouteDuplex' a
sum = dimap from to <<< gRouteDuplex end

-- | Builds a parser/printer from a record, where each record field corresponds
-- | to a constructor name for your data type.
-- |
-- | Note: this implicitly inserts calls to `end'` for each constructor, making
-- | the parser only valid for parsing URI suffixes. To parse URI prefixes, use `sumPrefix`.
sum'
:: forall a rep r
. Generic a rep
=> GRouteDuplex rep r
=> { | r }
-> RouteDuplex' a
sum' = dimap from to <<< gRouteDuplex end'

-- | A variation of `sum` that does not implicitly add an `end` to each branch.
-- | This is useful for defining sub-parsers that may consume only some of the
-- | URI segments, leaving the rest for subsequent parsers.
Expand Down
26 changes: 23 additions & 3 deletions src/Routing/Duplex/Parser.purs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ module Routing.Duplex.Parser
, boolean
, hash
, end
, end'
, module Routing.Duplex.Types
) where

Expand All @@ -42,6 +43,7 @@ import Data.String (Pattern(..), split)
import Data.String.CodeUnits as String
import Data.Traversable (traverse)
import Data.Tuple (Tuple(..))
import Data.Tuple.Nested ((/\))
import JSURI (decodeURIComponent)
import Routing.Duplex.Types (RouteParams, RouteState)

Expand All @@ -59,8 +61,11 @@ instance showRouteResult :: Show a => Show (RouteResult a) where
data RouteError
= Expected String String
| ExpectedEndOfPath String
| ExpectedNoHash String
| ExpectedNoParams RouteParams
| MissingParam String
| MalformedURIComponent String
| MissingHash
| EndOfPath

derive instance eqRouteError :: Eq RouteError
Expand Down Expand Up @@ -185,7 +190,12 @@ parsePath =
splitNonEmpty p s = split p s

toRouteState (Tuple (Tuple segments params) h) =
{ segments, params, hash: h }
{ segments
, params
, hash: case h of
"" -> Nothing
h' -> Just h'
}

splitAt k p str =
case String.indexOf (Pattern p) str of
Expand Down Expand Up @@ -213,7 +223,7 @@ take = Chomp \state ->
param :: String -> RouteParser String
param key = Chomp \state ->
case lookup key state.params of
Just a -> Success state a
Just a -> Success (state { params = Array.delete (key /\ a) state.params }) a
_ -> Fail $ MissingParam key

flag :: String -> RouteParser Boolean
Expand Down Expand Up @@ -259,14 +269,24 @@ int :: String -> Either String Int
int = maybe (Left "Int") Right <<< Int.fromString

hash :: RouteParser String
hash = Chomp \state -> Success state state.hash
hash = Chomp \state -> case state.hash of
Nothing -> Fail MissingHash
Just h -> Success (state { hash = Nothing }) h

end :: RouteParser Unit
end = Chomp \state ->
case Array.head state.segments of
Nothing -> Success state unit
Just str -> Fail (ExpectedEndOfPath str)

end' :: RouteParser Unit
end' = Chomp \state ->
case (Array.head state.segments /\ state.hash /\ state.params) of
(Nothing /\ Nothing /\ []) -> Success state unit
(Just str /\ _ /\ _) -> Fail (ExpectedEndOfPath str)
(_ /\ Just h /\ _) -> Fail (ExpectedNoHash h)
(_ /\ _ /\ params) -> Fail (ExpectedNoParams params)

boolean :: String -> Either String Boolean
boolean = case _ of
"true" -> Right true
Expand Down
8 changes: 5 additions & 3 deletions src/Routing/Duplex/Printer.purs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,9 @@ flag key val
| otherwise = mempty

hash :: String -> RoutePrinter
hash h = RoutePrinter _ { hash = h }
hash h
| h == "" = RoutePrinter _ { hash = Nothing }
| otherwise = RoutePrinter _ { hash = Just h }

run :: RoutePrinter -> String
run = printPath <<< applyFlipped emptyRouteState <<< unwrap
Expand All @@ -60,5 +62,5 @@ printPath { segments, params, hash: hash' } =
printParam key "" = encodeURIComponent key
printParam key val = encodeURIComponent key <> Just "=" <> encodeURIComponent val

printHash "" = ""
printHash h = "#" <> h
printHash Nothing = ""
printHash (Just h) = "#" <> h
5 changes: 3 additions & 2 deletions src/Routing/Duplex/Types.purs
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
module Routing.Duplex.Types where

import Data.Maybe (Maybe(..))
import Data.Tuple (Tuple)

type RouteParams = Array (Tuple String String)

type RouteState =
{ segments :: Array String
, params :: RouteParams
, hash :: String
, hash :: Maybe String
}

emptyRouteState :: RouteState
emptyRouteState =
{ segments: []
, params: []
, hash: ""
, hash: Nothing
}
1 change: 1 addition & 0 deletions test/Unit.purs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ combinatorUnitTests = do

-- hash
assertEqual { actual: parse hash "abc#def", expected: Right "def" }
assertEqual { actual: parse hash "abc", expected: Left MissingHash }

-- suffix
assertEqual { actual: parse (suffix segment "latest") "release/latest", expected: Right "release" }
Expand Down