packages feed

reroute 0.2.0.0 → 0.2.0.1

raw patch · 8 files changed

+283/−197 lines, 8 filesdep +criteriondep +randomdep +reroutedep ~basedep ~deepseqdep ~graph-corePVP: minor bump suggested

API additions: PVP suggests at least a minor version bump

Dependencies added: criterion, random, reroute

Dependency ranges changed: base, deepseq, graph-core, hashable, mtl, path-pieces, regex-compat, text, unordered-containers, vector

API changes (from Hackage documentation)

+ Data.PolyMap: alter :: (Typeable p, c p) => (Maybe (f (p -> a)) -> Maybe (f (p -> a))) -> PolyMap c f a -> PolyMap c f a
+ Data.PolyMap: ap :: Applicative f => PolyMap c f (a -> b) -> PolyMap c f a -> PolyMap c f b
+ Data.PolyMap: data PolyMap (c :: * -> Constraint) (f :: * -> *) (a :: *)
+ Data.PolyMap: empty :: PolyMap c f a
+ Data.PolyMap: insertWith :: (Typeable p, c p) => (f (p -> a) -> f (p -> a) -> f (p -> a)) -> f (p -> a) -> PolyMap c f a -> PolyMap c f a
+ Data.PolyMap: instance Alternative f => Monoid (PolyMap c f a)
+ Data.PolyMap: instance Functor f => Functor (PolyMap c f)
+ Data.PolyMap: lookup :: Typeable p => PolyMap c f a -> Maybe (f (p -> a))
+ Data.PolyMap: lookupApply :: (Typeable p, Functor f) => p -> PolyMap c f a -> Maybe (f a)
+ Data.PolyMap: lookupApplyAll :: Functor f => (forall p. c p => Maybe p) -> PolyMap c f a -> [f a]
+ Data.PolyMap: lookupConcat :: (Monoid m, Functor f) => (forall p. c p => Maybe p) -> (forall p. c p => p -> f (p -> a) -> m) -> PolyMap c f a -> m
+ Data.PolyMap: rnfHelper :: (forall p. c p => f (p -> a) -> ()) -> PolyMap c f a -> ()
+ Data.PolyMap: union :: Alternative f => PolyMap c f a -> PolyMap c f a -> PolyMap c f a
+ Data.PolyMap: unionWith :: (forall p. c p => f (p -> a) -> f (p -> a) -> f (p -> a)) -> PolyMap c f a -> PolyMap c f a -> PolyMap c f a
+ Data.PolyMap: updateAll :: (forall p. c p => f (p -> a) -> g (p -> b)) -> PolyMap c f a -> PolyMap c g b
+ Data.PolyMap: zip :: Applicative f => PolyMap c f a -> PolyMap c f b -> PolyMap c f (a, b)
+ Data.PolyMap: zipWith :: Applicative f => (a -> b -> d) -> PolyMap c f a -> PolyMap c f b -> PolyMap c f d
+ Data.PolyMap: zipWith' :: (forall p. c p => Maybe (f (p -> a)) -> Maybe (f (p -> b)) -> Maybe (f (p -> d))) -> PolyMap c f a -> PolyMap c f b -> PolyMap c f d

Files

+ benchmarks/Benchmarks.hs view
@@ -0,0 +1,75 @@+{-# LANGUAGE OverloadedStrings, DataKinds #-}++module Main where++import Web.Routing.TextRouting+import Web.Routing.SafeRouting++import Criterion.Main+import qualified Data.Text as T+import Data.List (permutations, foldl')+import System.Random (mkStdGen, randomRs)+import Data.Maybe (listToMaybe, fromMaybe)+import Data.Monoid (Monoid (..))+++buildRoutingTree :: [([T.Text], a)] -> RoutingTree a+buildRoutingTree =+  foldl' (\t (route, val) -> addToRoutingTree (joinSegs route) val t)+         emptyRoutingTree+  where joinSegs = T.intercalate "/"++lookupRoutingTreeM :: [[T.Text]] -> RoutingTree Int -> Int+lookupRoutingTreeM routes tree =+  foldl' (\z route -> maybe z snd (listToMaybe $ matchRoute' route tree)) 0 routes++buildPath :: [T.Text] -> Path '[]+buildPath = static . T.unpack . T.intercalate "/"++buildPathMap :: [([T.Text], a)] -> PathMap a+buildPathMap =+  foldl' (\t (route, val) -> insertPathMap' (buildPath route) (const val) t) mempty++lookupPathMapM :: [[T.Text]] -> PathMap Int -> Int+lookupPathMapM rs m =+  foldl' (\z route -> fromMaybe z (listToMaybe $ match m route)) 0 rs++benchmarks :: [Benchmark]+benchmarks =+  [ env setupTextMap $ \ ~(routingTree, routes') ->+    bgroup "TextRouting"+    [ bench "static-lookup" $ whnf (lookupRoutingTreeM routes') routingTree+    ]+  , env setupSafeMap $ \ ~(safeMap, routes') ->+    bgroup "SafeRouting"+    [ bench "static-lookup" $ whnf (lookupPathMapM routes') safeMap+    ]+  ]+  where+    strlen = 10+    seglen = 5+    num = 10+    routes = rndRoutes strlen seglen num+    routesList = zip routes [1..]+    setupTextMap = return (buildRoutingTree routesList, routes)+    setupSafeMap = return (buildPathMap routesList, routes)++main :: IO ()+main = defaultMain benchmarks++chunks :: Int -> [a] -> [[a]]+chunks n xs =+  let (ys, xs') = splitAt n xs+  in ys : chunks n xs'++-- | Generate a number of paths consisting of a fixed number of fixed length+-- strings ("path segments") where the content of the segments are letters in+-- random order. Contains all permutations with the path.+rndRoutes ::+     Int -- ^ Length of each string+  -> Int -- ^ Number of segments+  -> Int -- ^ Number of routes+  -> [[T.Text]]+rndRoutes strlen seglen num =+  take num $ concatMap permutations $ chunks seglen $ map T.pack $+    chunks strlen $ randomRs ('a', 'z') $ mkStdGen 1234
reroute.cabal view
@@ -1,5 +1,5 @@ name:                reroute-version:             0.2.0.0+version:             0.2.0.1 synopsis:            abstract implementation of typed and untyped web routing description:         abstraction over how urls with/without parameters are mapped to their corresponding handlers homepage:            http://github.com/agrafix/reroute@@ -13,8 +13,11 @@ cabal-version:       >=1.10  library-  exposed-modules:     Data.HVect, Web.Routing.AbstractRouter, Web.Routing.SafeRouting, Web.Routing.TextRouting-  other-modules:       Data.PolyMap+  exposed-modules:     Data.HVect,+                       Data.PolyMap,+                       Web.Routing.AbstractRouter,+                       Web.Routing.SafeRouting,+                       Web.Routing.TextRouting   build-depends:       base >=4.6 && <4.8,                        transformers >=0.3 && <0.5,                        text >= 0.11.3.1 && <1.3,@@ -23,7 +26,6 @@                        hashable >=1.2 && <1.3,                        mtl >=2.1 && <2.3,                        path-pieces >=0.1 && <0.2,-                       hspec2 >=0.4 && <0.5,                        vector >=0.10 && <0.11,                        deepseq >= 1.1.0.2 && < 1.4,                        graph-core >=0.2 && <0.3@@ -31,12 +33,13 @@   default-language:    Haskell2010  -test-suite reroutetest+test-suite reroute-test   type:                exitcode-stdio-1.0-  hs-source-dirs:      src-  main-is:             Web/Routing/Specs/Main.hs-  other-modules:       Web.Routing.Specs.SafeRoutingSpec, Web.Routing.Specs.TextRoutingSpec+  hs-source-dirs:      test+  main-is:             Spec.hs+  other-modules:       Web.Routing.SafeRoutingSpec, Web.Routing.TextRoutingSpec   build-depends:       base >=4.6 && <4.8,+                       reroute,                        transformers >=0.3 && <0.5,                        text >= 0.11.3.1 && <1.3,                        unordered-containers ==0.2.*,@@ -50,6 +53,26 @@                        graph-core >=0.2 && <0.3   default-language:    Haskell2010   ghc-options: -Wall -fno-warn-orphans++benchmark reroute-benchmarks+  type:             exitcode-stdio-1.0+  ghc-options:      -Wall -O2+  hs-source-dirs:   src benchmarks+  default-language: Haskell2010+  main-is:          Benchmarks.hs+  build-depends:+    base,+    criterion,+    text,+    mtl,+    unordered-containers,+    vector,+    hashable,+    regex-compat,+    random,+    deepseq,+    path-pieces,+    graph-core  source-repository head   type:     git
− src/Web/Routing/Specs/Main.hs
@@ -1,10 +0,0 @@-import qualified Web.Routing.Specs.TextRoutingSpec-import qualified Web.Routing.Specs.SafeRoutingSpec--import Test.Hspec--main :: IO ()-main =-    hspec $-    do Web.Routing.Specs.TextRoutingSpec.spec-       Web.Routing.Specs.SafeRoutingSpec.spec
− src/Web/Routing/Specs/SafeRoutingSpec.hs
@@ -1,98 +0,0 @@-{-# LANGUAGE CPP #-}-{-# LANGUAGE DataKinds #-}-{-# LANGUAGE TypeFamilies #-}-{-# LANGUAGE OverloadedStrings #-}-#if MIN_VERSION_base(4,7,0)-{-# LANGUAGE AllowAmbiguousTypes #-}-#endif-module Web.Routing.Specs.SafeRoutingSpec where--import Test.Hspec-import Data.HVect--import Control.Monad.Identity-import Web.Routing.SafeRouting-import Web.Routing.AbstractRouter-import Data.Monoid (mconcat)-import Control.Applicative (Applicative (..))-import qualified Data.Text as T--data ReturnVar-   = IntVar Int-   | StrVar T.Text-   | BoolVar Bool-   | ListVar [ReturnVar]-   deriving (Show, Eq, Read)--defR :: (Monad m, HVectElim ts (m ReturnVar) ~ HVectElim ts x) => Path ts -> HVectElim ts x -> RegistryT (SafeRouter m ReturnVar) middleware Bool m ()-defR path action = hookRoute True (SafeRouterPath path) (HVectElim' action)---spec :: Spec-spec =-    describe "SafeRouting Spec" $-    do it "should match known routes" $-          do checkRoute "" [StrVar "root"]-             checkRoute "/bar" [StrVar "bar"]-       it "shoudn't match unknown routes" $-          do checkRoute "/random" []-             checkRoute "/baz" []-       it "should capture variables in routes" $-          do checkRoute "/bar/23/baz" [IntVar 23]-             checkRoute "/bar/23/baz/100" [ListVar [IntVar 23, IntVar 100]]-             checkRoute "/bar/23/100" [ListVar [IntVar 23, IntVar 100]]-             checkRoute "/entry/344/2014-20-14T12:23" [ListVar [IntVar 344, StrVar "2014-20-14T12:23"]]-             checkRoute "/entry/bytags/344/2014-20-14T12:23" [ListVar [IntVar 344, StrVar "2014-20-14T12:23"]]-             checkRoute "/entry/2/rel/3"  [ListVar [IntVar 2, IntVar 3]]-       it "should handle multiple possible matches correctly" $-          do checkRoute "/bar/5" [IntVar 5, StrVar "5"]-             checkRoute "/bar/bingo" [StrVar "bar/bingo", StrVar "bingo"]-             checkRoute "/entry/1/audit" [IntVar 1,ListVar [IntVar 1,StrVar "audit"]]-       it "should provide an Applicative interface" $-          do let numbers =-                   mconcat-                   [ singleton var id-                   , singleton ("forty" </> "two") (42 :: Int)-                   ]-                 operators =-                   mconcat-                   [ singleton "plus" ((+) :: Int -> Int -> Int)-                   , singleton "mult" (*)-                   ]-                 routes = operators <*> numbers <*> numbers-                 check path val = match routes (pieces path) `shouldBe` [val]-             check "/plus/forty/two/forty/two" (42+42)-             check "/mult/forty/two/3" (42*3)-             check "/plus/5/89" 94-    where-      pieces :: T.Text -> [T.Text]-      pieces = filter (not . T.null) . T.splitOn "/"-      -      checkRoute :: T.Text -> [ReturnVar] -> Expectation-      checkRoute r x =-          let matches = handleFun (pieces r)-          in (map (runIdentity . snd) matches) `shouldBe` x--      handleFun :: [T.Text] -> [(ParamMap, Identity ReturnVar)]-      handleFun = handleFun' True-      (_, handleFun', _) =-          runIdentity (runRegistry SafeRouter handleDefs)--      handleDefs =-          do defR root $ return (StrVar "root")-             defR "bar" $ return (StrVar "bar")-             defR ("bar" </> var) (return . IntVar)-             defR ("bar" </> var </> "baz") (return . IntVar)-             defR ("bar" </> var </> "baz" </> var) $ \i i2 ->-                 return (ListVar [IntVar i, IntVar i2])-             defR ("bar" </> var </> var) $ \i i2 ->-                 return (ListVar [IntVar i, IntVar i2])-             defR ("entry" </> var </> var) $ \i st ->-                 return (ListVar [IntVar i, StrVar st])-             defR ("entry/bytags" </> var </> var) $ \i st ->-                 return (ListVar [IntVar i, StrVar st])-             defR ("entry" </> var </> "rel" </> var) $ \i i2 ->-                 return (ListVar [IntVar i, IntVar i2])-             defR ("bar" </> "bingo") $ return (StrVar "bar/bingo")-             defR ("bar" </> var) $ (return . StrVar . T.pack)-             defR ("entry" </> var </> "audit") (return . IntVar)
− src/Web/Routing/Specs/TextRoutingSpec.hs
@@ -1,81 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}-module Web.Routing.Specs.TextRoutingSpec (spec) where--import Test.Hspec--import Web.Routing.TextRouting-import qualified Web.Routing.AbstractRouter as R-import qualified Data.HashMap.Strict as HM--spec :: Spec-spec =-    do matchNodeDesc-       matchRouteDesc-       parseRouteNodeDesc--matchNodeDesc :: Spec-matchNodeDesc =-    describe "matchNode" $-    do it "shouldn't match to root node" $-          matchNode "foo" RouteNodeRoot `shouldBe` (False, Nothing)-       it "should capture basic variables" $-          matchNode "123" (RouteNodeCapture (R.CaptureVar "x")) `shouldBe` (True, Just (R.CaptureVar "x", "123"))-       it "should work with regex" $-          matchNode "123" (RouteNodeRegex (R.CaptureVar "x") (buildRegex "^[0-9]+$")) `shouldBe` (True, Just (R.CaptureVar "x", "123"))--matchRouteDesc :: Spec-matchRouteDesc =-    describe "matchRoute" $-    do it "shouldn't match unknown routes" $-          do matchRoute "random" routingTree `shouldBe` noMatches-             matchRoute "/baz" routingTree `shouldBe` noMatches-       it "should match known routes" $-          do matchRoute "/" routingTree `shouldBe` oneMatch emptyParamMap [1]-             matchRoute "/bar" routingTree `shouldBe` oneMatch emptyParamMap [2]-       it "should capture variables in routes" $-          do matchRoute "/bar/5" routingTree `shouldBe` oneMatch (vMap [("baz", "5")]) [3]-             matchRoute "/bar/23/baz" routingTree `shouldBe` oneMatch (vMap [("baz", "23")]) [4]-             matchRoute "/bar/23/baz/100" routingTree `shouldBe` oneMatch (vMap [("baz", "23"), ("bim", "100")]) [4]-             matchRoute "/ba/23/100" routingTree `shouldBe` oneMatch (vMap [("baz", "23"), ("bim", "100")]) [4]-             matchRoute "/entry/344/2014-20-14T12:23" routingTree `shouldBe` oneMatch (vMap [("cid", "344"), ("since", "2014-20-14T12:23")]) [6]-             matchRoute "/entry/bytags/344/2014-20-14T12:23" routingTree `shouldBe` oneMatch (vMap [("cid", "344"), ("since", "2014-20-14T12:23")]) [7]-             matchRoute "/entry/2/rel/3" routingTree `shouldBe` oneMatch (vMap [("eid", "2"), ("cid", "3")]) [9]-       it "should handle multiple possibile matches correctly" $-          do matchRoute "/bar/bingo" routingTree `shouldBe` multiMatch-             matchRoute "/entry/1/audit" routingTree `shouldBe` multiMatch'-    where-      vMap kv =-          HM.fromList $ map (\(k, v) -> (R.CaptureVar k, v)) kv-      multiMatch =-          ((oneMatch emptyParamMap [5])-            ++ oneMatch (vMap [("baz", "bingo")]) [3])-      multiMatch' =-          ((oneMatch (vMap [("eid", "1")]) [8])-           ++ (oneMatch (vMap [("since", "audit"), ("cid", "1")]) [6]))-      noMatches = []-      oneMatch pm m = [(pm, m)]-      routingTree =-          foldl (\tree (route, action) -> addToRoutingTree route action tree) emptyRoutingTree routes-      routes =-          [ ("/", [1])-          , ("/bar", [2 :: Int])-          , ("/bar/:baz", [3])-          , ("/bar/bingo", [5])-          , ("/bar/:baz/baz", [4])-          , ("/bar/:baz/baz/:bim", [4])-          , ("/ba/:baz/:bim", [4])-          , ("/entry/:cid/:since", [6])-          , ("/entry/bytags/:cid/:since", [7])-          , ("/entry/:eid/audit", [8])-          , ("/entry/:eid/rel/:cid", [9])-          ]--parseRouteNodeDesc :: Spec-parseRouteNodeDesc =-    describe "parseRouteNode" $-    do it "parses text nodes correctly" $-          parseRouteNode "foo" `shouldBe` RouteNodeText "foo"-       it "parses capture variables" $-          parseRouteNode ":bar" `shouldBe` RouteNodeCapture (R.CaptureVar "bar")-       it "parses regex capture variables" $-          parseRouteNode "{bar:^[0-9]$}" `shouldBe` RouteNodeRegex (R.CaptureVar "bar") (buildRegex "^[0-9]$")
+ test/Spec.hs view
@@ -0,0 +1,1 @@+{-# OPTIONS_GHC -F -pgmF hspec-discover #-}
+ test/Web/Routing/SafeRoutingSpec.hs view
@@ -0,0 +1,95 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE OverloadedStrings #-}+module Web.Routing.SafeRoutingSpec where++import Test.Hspec+import Data.HVect++import Control.Monad.Identity+import Web.Routing.SafeRouting+import Web.Routing.AbstractRouter+import Data.Monoid (mconcat)+import Control.Applicative (Applicative (..))+import qualified Data.Text as T++data ReturnVar+   = IntVar Int+   | StrVar T.Text+   | BoolVar Bool+   | ListVar [ReturnVar]+   deriving (Show, Eq, Read)++defR :: (Monad m, m ReturnVar ~ x) => Path ts -> HVectElim ts x -> RegistryT (SafeRouter m ReturnVar) middleware Bool m ()+defR path action = hookRoute True (SafeRouterPath path) (HVectElim' action)+++spec :: Spec+spec =+    describe "SafeRouting Spec" $+    do it "should match known routes" $+          do checkRoute "" [StrVar "root"]+             checkRoute "/bar" [StrVar "bar"]+       it "shoudn't match unknown routes" $+          do checkRoute "/random" []+             checkRoute "/baz" []+       it "should capture variables in routes" $+          do checkRoute "/bar/23/baz" [IntVar 23]+             checkRoute "/bar/23/baz/100" [ListVar [IntVar 23, IntVar 100]]+             checkRoute "/bar/23/100" [ListVar [IntVar 23, IntVar 100]]+             checkRoute "/entry/344/2014-20-14T12:23" [ListVar [IntVar 344, StrVar "2014-20-14T12:23"]]+             checkRoute "/entry/bytags/344/2014-20-14T12:23" [ListVar [IntVar 344, StrVar "2014-20-14T12:23"]]+             checkRoute "/entry/2/rel/3"  [ListVar [IntVar 2, IntVar 3]]+       it "should handle multiple possible matches correctly" $+          do checkRoute "/bar/5" [IntVar 5, StrVar "5"]+             checkRoute "/bar/bingo" [StrVar "bar/bingo", StrVar "bingo"]+             checkRoute "/entry/1/audit" [IntVar 1,ListVar [IntVar 1,StrVar "audit"]]+       it "should provide an Applicative interface" $+          do let numbers =+                   mconcat+                   [ singleton var id+                   , singleton ("forty" </> "two") (42 :: Int)+                   ]+                 operators =+                   mconcat+                   [ singleton "plus" ((+) :: Int -> Int -> Int)+                   , singleton "mult" (*)+                   ]+                 routes = operators <*> numbers <*> numbers+                 check path val = match routes (pieces path) `shouldBe` [val]+             check "/plus/forty/two/forty/two" (42+42)+             check "/mult/forty/two/3" (42*3)+             check "/plus/5/89" 94+    where+      pieces :: T.Text -> [T.Text]+      pieces = filter (not . T.null) . T.splitOn "/"+      +      checkRoute :: T.Text -> [ReturnVar] -> Expectation+      checkRoute r x =+          let matches = handleFun (pieces r)+          in (map (runIdentity . snd) matches) `shouldBe` x++      handleFun :: [T.Text] -> [(ParamMap, Identity ReturnVar)]+      handleFun = handleFun' True+      (_, handleFun', _) =+          runIdentity (runRegistry SafeRouter handleDefs)++      handleDefs =+          do defR root $ return (StrVar "root")+             defR "bar" $ return (StrVar "bar")+             defR ("bar" </> var) (return . IntVar)+             defR ("bar" </> var </> "baz") (return . IntVar)+             defR ("bar" </> var </> "baz" </> var) $ \i i2 ->+                 return (ListVar [IntVar i, IntVar i2])+             defR ("bar" </> var </> var) $ \i i2 ->+                 return (ListVar [IntVar i, IntVar i2])+             defR ("entry" </> var </> var) $ \i st ->+                 return (ListVar [IntVar i, StrVar st])+             defR ("entry/bytags" </> var </> var) $ \i st ->+                 return (ListVar [IntVar i, StrVar st])+             defR ("entry" </> var </> "rel" </> var) $ \i i2 ->+                 return (ListVar [IntVar i, IntVar i2])+             defR ("bar" </> "bingo") $ return (StrVar "bar/bingo")+             defR ("bar" </> var) $ (return . StrVar . T.pack)+             defR ("entry" </> var </> "audit") (return . IntVar)
+ test/Web/Routing/TextRoutingSpec.hs view
@@ -0,0 +1,81 @@+{-# LANGUAGE OverloadedStrings #-}+module Web.Routing.TextRoutingSpec (spec) where++import Test.Hspec++import Web.Routing.TextRouting+import qualified Web.Routing.AbstractRouter as R+import qualified Data.HashMap.Strict as HM++spec :: Spec+spec =+    do matchNodeDesc+       matchRouteDesc+       parseRouteNodeDesc++matchNodeDesc :: Spec+matchNodeDesc =+    describe "matchNode" $+    do it "shouldn't match to root node" $+          matchNode "foo" RouteNodeRoot `shouldBe` (False, Nothing)+       it "should capture basic variables" $+          matchNode "123" (RouteNodeCapture (R.CaptureVar "x")) `shouldBe` (True, Just (R.CaptureVar "x", "123"))+       it "should work with regex" $+          matchNode "123" (RouteNodeRegex (R.CaptureVar "x") (buildRegex "^[0-9]+$")) `shouldBe` (True, Just (R.CaptureVar "x", "123"))++matchRouteDesc :: Spec+matchRouteDesc =+    describe "matchRoute" $+    do it "shouldn't match unknown routes" $+          do matchRoute "random" routingTree `shouldBe` noMatches+             matchRoute "/baz" routingTree `shouldBe` noMatches+       it "should match known routes" $+          do matchRoute "/" routingTree `shouldBe` oneMatch emptyParamMap [1]+             matchRoute "/bar" routingTree `shouldBe` oneMatch emptyParamMap [2]+       it "should capture variables in routes" $+          do matchRoute "/bar/5" routingTree `shouldBe` oneMatch (vMap [("baz", "5")]) [3]+             matchRoute "/bar/23/baz" routingTree `shouldBe` oneMatch (vMap [("baz", "23")]) [4]+             matchRoute "/bar/23/baz/100" routingTree `shouldBe` oneMatch (vMap [("baz", "23"), ("bim", "100")]) [4]+             matchRoute "/ba/23/100" routingTree `shouldBe` oneMatch (vMap [("baz", "23"), ("bim", "100")]) [4]+             matchRoute "/entry/344/2014-20-14T12:23" routingTree `shouldBe` oneMatch (vMap [("cid", "344"), ("since", "2014-20-14T12:23")]) [6]+             matchRoute "/entry/bytags/344/2014-20-14T12:23" routingTree `shouldBe` oneMatch (vMap [("cid", "344"), ("since", "2014-20-14T12:23")]) [7]+             matchRoute "/entry/2/rel/3" routingTree `shouldBe` oneMatch (vMap [("eid", "2"), ("cid", "3")]) [9]+       it "should handle multiple possibile matches correctly" $+          do matchRoute "/bar/bingo" routingTree `shouldBe` multiMatch+             matchRoute "/entry/1/audit" routingTree `shouldBe` multiMatch'+    where+      vMap kv =+          HM.fromList $ map (\(k, v) -> (R.CaptureVar k, v)) kv+      multiMatch =+          ((oneMatch emptyParamMap [5])+            ++ oneMatch (vMap [("baz", "bingo")]) [3])+      multiMatch' =+          ((oneMatch (vMap [("eid", "1")]) [8])+           ++ (oneMatch (vMap [("since", "audit"), ("cid", "1")]) [6]))+      noMatches = []+      oneMatch pm m = [(pm, m)]+      routingTree =+          foldl (\tree (route, action) -> addToRoutingTree route action tree) emptyRoutingTree routes+      routes =+          [ ("/", [1])+          , ("/bar", [2 :: Int])+          , ("/bar/:baz", [3])+          , ("/bar/bingo", [5])+          , ("/bar/:baz/baz", [4])+          , ("/bar/:baz/baz/:bim", [4])+          , ("/ba/:baz/:bim", [4])+          , ("/entry/:cid/:since", [6])+          , ("/entry/bytags/:cid/:since", [7])+          , ("/entry/:eid/audit", [8])+          , ("/entry/:eid/rel/:cid", [9])+          ]++parseRouteNodeDesc :: Spec+parseRouteNodeDesc =+    describe "parseRouteNode" $+    do it "parses text nodes correctly" $+          parseRouteNode "foo" `shouldBe` RouteNodeText "foo"+       it "parses capture variables" $+          parseRouteNode ":bar" `shouldBe` RouteNodeCapture (R.CaptureVar "bar")+       it "parses regex capture variables" $+          parseRouteNode "{bar:^[0-9]$}" `shouldBe` RouteNodeRegex (R.CaptureVar "bar") (buildRegex "^[0-9]$")