packages feed

nested-routes 0.1.1 → 0.2

raw patch · 5 files changed

+376/−122 lines, 5 filesdep +attoparsecdep +pred-triedep −pseudo-triePVP ok

version bump matches the API change (PVP)

Dependencies added: attoparsec, pred-trie

Dependencies removed: pseudo-trie

API changes (from Hackage documentation)

- Web.Routes.Nested: handle :: Monad m => [Text] -> VerbListenerT z Response m () -> [HandlerT z m ()] -> HandlerT z m ()
+ Web.Routes.Nested: handleLit :: (Monad m, Singleton (UrlChunks xs) (ExpectArity xs (Either (VerbListenerT z (FileExtListenerT Response m ()) m ()) (VerbListenerT z Response m ()))) (RUPTrie Text (Either (VerbListenerT z (FileExtListenerT Response m ()) m ()) (VerbListenerT z Response m ()))), Extrude (UrlChunks xs) (RUPTrie Text (Either (VerbListenerT z (FileExtListenerT Response m ()) m ()) (VerbListenerT z Response m ()))) (RUPTrie Text (Either (VerbListenerT z (FileExtListenerT Response m ()) m ()) (VerbListenerT z Response m ())))) => UrlChunks xs -> ExpectArity xs (Either (VerbListenerT z (FileExtListenerT Response m ()) m ()) (VerbListenerT z Response m ())) -> [HandlerT z m ()] -> HandlerT z m ()
+ Web.Routes.Nested: handleParse :: (Monad m, Singleton (UrlChunks xs) (ExpectArity xs (Either (VerbListenerT z (FileExtListenerT Response m ()) m ()) (VerbListenerT z Response m ()))) (RUPTrie Text (Either (VerbListenerT z (FileExtListenerT Response m ()) m ()) (VerbListenerT z Response m ()))), Extrude (UrlChunks xs) (RUPTrie Text (Either (VerbListenerT z (FileExtListenerT Response m ()) m ()) (VerbListenerT z Response m ()))) (RUPTrie Text (Either (VerbListenerT z (FileExtListenerT Response m ()) m ()) (VerbListenerT z Response m ())))) => UrlChunks xs -> ExpectArity xs (Either (VerbListenerT z (FileExtListenerT Response m ()) m ()) (VerbListenerT z Response m ())) -> [HandlerT z m ()] -> HandlerT z m ()
+ Web.Routes.Nested.FileExtListener: blazeOnly :: Html -> Response
+ Web.Routes.Nested.FileExtListener: builderOnly :: Builder -> RequestHeaders -> Response
+ Web.Routes.Nested.FileExtListener: bytestringOnly :: ByteString -> RequestHeaders -> Response
+ Web.Routes.Nested.FileExtListener: jsonOnly :: (ToJSON j) => j -> Response
+ Web.Routes.Nested.FileExtListener: jsonpOnly :: (ToJSON j) => j -> Response
+ Web.Routes.Nested.FileExtListener: lucidOnly :: (Monad m) => HtmlT m () -> m Response
+ Web.Routes.Nested.FileExtListener: textOnly :: Text -> Response
+ Web.Routes.Nested.Types: class Extend eitherUrlChunk child result | eitherUrlChunk child -> result
+ Web.Routes.Nested.Types: class Extrude chunks start result | chunks start -> result
+ Web.Routes.Nested.Types: class Singleton chunks a trie | chunks a -> trie
+ Web.Routes.Nested.Types: class ToL chunks
+ Web.Routes.Nested.Types: class ToNE chunks
+ Web.Routes.Nested.Types: extend :: Extend eitherUrlChunk child result => eitherUrlChunk -> child -> result
+ Web.Routes.Nested.Types: extrude :: Extrude chunks start result => chunks -> start -> result
+ Web.Routes.Nested.Types: iResultToMaybe :: IResult Text r -> Maybe r
+ Web.Routes.Nested.Types: instance [overlap ok] (Extrude (UrlChunks xs) trie0 trie1, Extend (EitherUrlChunk x) trie1 trie2) => Extrude (UrlChunks (x : xs)) trie0 trie2
+ Web.Routes.Nested.Types: instance [overlap ok] (Singleton (UrlChunks xs) a trie0, Extend (EitherUrlChunk x) trie0 trie1) => Singleton (UrlChunks (x : xs)) a trie1
+ Web.Routes.Nested.Types: instance [overlap ok] Extend (EitherUrlChunk 'Nothing) (RUPTrie Text a) (RUPTrie Text a)
+ Web.Routes.Nested.Types: instance [overlap ok] Extend (EitherUrlChunk ('Just r)) (RUPTrie Text (r -> a)) (RUPTrie Text a)
+ Web.Routes.Nested.Types: instance [overlap ok] Extrude (UrlChunks '[]) (RUPTrie Text a) (RUPTrie Text a)
+ Web.Routes.Nested.Types: instance [overlap ok] Singleton (UrlChunks '[]) a (RUPTrie Text a)
+ Web.Routes.Nested.Types: instance [overlap ok] ToL (UrlChunks '[])
+ Web.Routes.Nested.Types: instance [overlap ok] ToL (UrlChunks xs) => ToL (UrlChunks ('Nothing : xs))
+ Web.Routes.Nested.Types: instance [overlap ok] ToNE (UrlChunks '['Nothing])
+ Web.Routes.Nested.Types: instance [overlap ok] ToNE (UrlChunks xs) => ToNE (UrlChunks ('Nothing : xs))
+ Web.Routes.Nested.Types: restAreLits :: UrlChunks xs -> Bool
+ Web.Routes.Nested.Types: singleton :: Singleton chunks a trie => chunks -> a -> trie
+ Web.Routes.Nested.Types: toL :: ToL chunks => chunks -> [Text]
+ Web.Routes.Nested.Types: toNE :: ToNE chunks => chunks -> NonEmpty Text
+ Web.Routes.Nested.Types.UrlChunks: (#) :: UrlChunks ([] (Maybe *))
+ Web.Routes.Nested.Types.UrlChunks: (</>) :: EitherUrlChunk mx -> UrlChunks xs -> UrlChunks ((:) (Maybe *) mx xs)
+ Web.Routes.Nested.Types.UrlChunks: [:=] :: Text -> EitherUrlChunk Nothing
+ Web.Routes.Nested.Types.UrlChunks: [:~] :: (Text, Parser r) -> EitherUrlChunk (Just r)
+ Web.Routes.Nested.Types.UrlChunks: [Cons] :: EitherUrlChunk mx -> UrlChunks xs -> UrlChunks (mx : xs)
+ Web.Routes.Nested.Types.UrlChunks: [Root] :: UrlChunks []
+ Web.Routes.Nested.Types.UrlChunks: data EitherUrlChunk (x :: Maybe *)
+ Web.Routes.Nested.Types.UrlChunks: data UrlChunks (xs :: [Maybe *])
+ Web.Routes.Nested.VerbListener: foldMWithKey :: Monad m => (acc -> Verb -> a -> m acc) -> acc -> Map Verb a -> m acc
- Web.Routes.Nested: [HandlerT] :: WriterT (MergeRooted Text (Verbs z m Response), MergeRooted Text (FileExts Response)) m a -> HandlerT z m a
+ Web.Routes.Nested: [HandlerT] :: WriterT (RUPTrie Text (Either (VerbListenerT z (FileExtListenerT Response m ()) m ()) (VerbListenerT z Response m ())), RUPTrie Text (Either (VerbListenerT z (FileExtListenerT Response m ()) m ()) (VerbListenerT z Response m ()))) m a -> HandlerT z m a
- Web.Routes.Nested: [runHandler] :: HandlerT z m a -> WriterT (MergeRooted Text (Verbs z m Response), MergeRooted Text (FileExts Response)) m a
+ Web.Routes.Nested: [runHandler] :: HandlerT z m a -> WriterT (RUPTrie Text (Either (VerbListenerT z (FileExtListenerT Response m ()) m ()) (VerbListenerT z Response m ())), RUPTrie Text (Either (VerbListenerT z (FileExtListenerT Response m ()) m ()) (VerbListenerT z Response m ()))) m a
- Web.Routes.Nested: notFound :: Monad m => [Text] -> FileExtListenerT Response m () -> HandlerT z m ()
+ Web.Routes.Nested: notFound :: (Monad m, Singleton (UrlChunks xs) (ExpectArity xs (Either (VerbListenerT z (FileExtListenerT Response m ()) m ()) (VerbListenerT z Response m ()))) (RUPTrie Text (Either (VerbListenerT z (FileExtListenerT Response m ()) m ()) (VerbListenerT z Response m ())))) => UrlChunks xs -> ExpectArity xs (Either (VerbListenerT z (FileExtListenerT Response m ()) m ()) (VerbListenerT z Response m ())) -> HandlerT z m ()
- Web.Routes.Nested.VerbListener: [Verbs] :: Map Verb (Maybe (ReaderT ByteString m z, Maybe BodyLength), FileExts r) -> Verbs z m r
+ Web.Routes.Nested.VerbListener: [Verbs] :: Map Verb (Maybe (ReaderT ByteString m z, Maybe BodyLength), r) -> Verbs z m r
- Web.Routes.Nested.VerbListener: [unVerbs] :: Verbs z m r -> Map Verb (Maybe (ReaderT ByteString m z, Maybe BodyLength), FileExts r)
+ Web.Routes.Nested.VerbListener: [unVerbs] :: Verbs z m r -> Map Verb (Maybe (ReaderT ByteString m z, Maybe BodyLength), r)
- Web.Routes.Nested.VerbListener: delete :: (Monad m) => FileExtListenerT Response m a -> VerbListenerT z Response m ()
+ Web.Routes.Nested.VerbListener: delete :: (Monad m) => a -> VerbListenerT z a m ()
- Web.Routes.Nested.VerbListener: get :: (Monad m) => FileExtListenerT Response m a -> VerbListenerT z Response m ()
+ Web.Routes.Nested.VerbListener: get :: (Monad m) => a -> VerbListenerT z a m ()
- Web.Routes.Nested.VerbListener: post :: (Monad m, MonadIO m) => (ByteString -> m z) -> FileExtListenerT Response m a -> VerbListenerT z Response m ()
+ Web.Routes.Nested.VerbListener: post :: (Monad m, MonadIO m) => (ByteString -> m z) -> a -> VerbListenerT z a m ()
- Web.Routes.Nested.VerbListener: postMax :: (Monad m, MonadIO m) => BodyLength -> (ByteString -> m z) -> FileExtListenerT Response m a -> VerbListenerT z Response m ()
+ Web.Routes.Nested.VerbListener: postMax :: (Monad m, MonadIO m) => BodyLength -> (ByteString -> m z) -> a -> VerbListenerT z a m ()
- Web.Routes.Nested.VerbListener: put :: (Monad m, MonadIO m) => (ByteString -> m z) -> FileExtListenerT Response m a -> VerbListenerT z Response m ()
+ Web.Routes.Nested.VerbListener: put :: (Monad m, MonadIO m) => (ByteString -> m z) -> a -> VerbListenerT z a m ()
- Web.Routes.Nested.VerbListener: putMax :: (Monad m, MonadIO m) => BodyLength -> (ByteString -> m z) -> FileExtListenerT Response m a -> VerbListenerT z Response m ()
+ Web.Routes.Nested.VerbListener: putMax :: (Monad m, MonadIO m) => BodyLength -> (ByteString -> m z) -> a -> VerbListenerT z a m ()

Files

nested-routes.cabal view
@@ -1,5 +1,5 @@ Name:                   nested-routes-Version:                0.1.1+Version:                0.2 Author:                 Athan Clark <athan.clark@gmail.com> Maintainer:             Athan Clark <athan.clark@gmail.com> License:                BSD3@@ -14,6 +14,7 @@   HS-Source-Dirs:       src   GHC-Options:          -Wall   Exposed-Modules:      Web.Routes.Nested+                        Web.Routes.Nested.Types                         Web.Routes.Nested.VerbListener                         Web.Routes.Nested.FileExtListener   Build-Depends:        base >= 4 && < 5@@ -24,12 +25,13 @@                       , transformers                       , semigroups                       , containers-                      , pseudo-trie >= 0.0.4.3                       , text                       , aeson                       , blaze-html                       , lucid                       , bytestring+                      , attoparsec+                      , pred-trie >= 0.0.6.1  Test-Suite spec   Type:                 exitcode-stdio-1.0
src/Web/Routes/Nested.hs view
@@ -1,20 +1,28 @@-{-# LANGUAGE DeriveFunctor              #-}-{-# LANGUAGE GADTs                      #-}-{-# LANGUAGE GeneralizedNewtypeDeriving #-}-{-# LANGUAGE ScopedTypeVariables        #-}-{-# LANGUAGE StandaloneDeriving         #-}-{-# LANGUAGE TypeOperators              #-}-{-# LANGUAGE OverloadedStrings          #-}+{-# LANGUAGE+    DeriveFunctor+  , GADTs+  , GeneralizedNewtypeDeriving+  , ScopedTypeVariables+  , StandaloneDeriving+  , TypeOperators+  , OverloadedStrings+  , DataKinds+  , TupleSections+  , FlexibleContexts+  #-}  module Web.Routes.Nested   ( module Web.Routes.Nested.FileExtListener   , module Web.Routes.Nested.VerbListener+  , module Web.Routes.Nested.Types   , HandlerT (..)-  , handle+  , handleLit+  , handleParse   , notFound   , route   ) where +import           Web.Routes.Nested.Types import           Web.Routes.Nested.FileExtListener import           Web.Routes.Nested.VerbListener @@ -30,10 +38,8 @@ import           Control.Monad.Reader import qualified Data.List.NonEmpty                as NE import           Data.Monoid-import           Data.Trie.Pseudo-import qualified Data.Trie.Pseudo                  as P-import           Data.Trie.Rooted-import qualified Data.Trie.Rooted                  as R+import           Data.Trie.Pred.Unified+import qualified Data.Trie.Pred.Unified            as P import           Data.Traversable import qualified Data.Text                         as T import qualified Data.Map.Lazy                     as M@@ -42,8 +48,8 @@   newtype HandlerT z m a = HandlerT-  { runHandler :: WriterT ( MergeRooted T.Text (Verbs z m Response)-                          , MergeRooted T.Text (FileExts Response) ) m a }+  { runHandler :: WriterT ( RUPTrie T.Text (Either (VerbListenerT z (FileExtListenerT Response m ()) m ()) (VerbListenerT z Response m ()))+                          , RUPTrie T.Text (Either (VerbListenerT z (FileExtListenerT Response m ()) m ()) (VerbListenerT z Response m ())) ) m a }   deriving (Functor)  deriving instance Applicative m => Applicative (HandlerT z m)@@ -53,41 +59,91 @@   lift ma = HandlerT $ lift ma  --- | Add a path to the list of routes-handle :: Monad m =>-          [T.Text]                      -- ^ Input path, separated by slashes-       -> VerbListenerT z Response m () -- ^ HTTP Method-oriented monad-       -> [HandlerT z m ()]             -- ^ Child paths-       -> HandlerT z m ()-handle ts vl [] = do-  vfrs <- lift $ execWriterT $ runVerbListenerT vl+handleLit :: ( Monad m+             , Singleton (UrlChunks xs)+                 (ExpectArity xs+                           (Either+                              (VerbListenerT z (FileExtListenerT Response m ()) m ())+                              (VerbListenerT z Response m ())))+                 (RUPTrie T.Text+                           (Either+                              (VerbListenerT z (FileExtListenerT Response m ()) m ())+                              (VerbListenerT z Response m ())))+             , Extrude (UrlChunks xs)+                 (RUPTrie T.Text+                           (Either+                              (VerbListenerT z (FileExtListenerT Response m ()) m ())+                              (VerbListenerT z Response m ())))+                 (RUPTrie T.Text+                           (Either+                              (VerbListenerT z (FileExtListenerT Response m ()) m ())+                              (VerbListenerT z Response m ())))+             ) =>+             UrlChunks xs+          -> ExpectArity xs (Either (VerbListenerT z (FileExtListenerT Response m ()) m ()) (VerbListenerT z Response m ()))+          -> [HandlerT z m ()]+          -> HandlerT z m ()+handleLit ts vl [] =+  HandlerT $ tell (singleton ts vl, mempty)+handleLit ts vl cs = do+  (child,_) <- lift $ foldM (\acc c -> (acc <>) <$> (execWriterT $ runHandler c)) mempty cs -  HandlerT $ tell $-    case ts of-      [] -> (MergeRooted $ Rooted (Just vfrs) [],                           mempty)-      _  -> (MergeRooted $ Rooted Nothing     [Rest (NE.fromList ts) vfrs], mempty)-handle ts vl cs = do-  vfrs <- lift $ execWriterT $ runVerbListenerT vl+  HandlerT $ tell $ let+                      child' = extrude ts child+                    in+                    (P.merge child' $ singleton ts vl, mempty)++handleParse :: ( Monad m+               , Singleton (UrlChunks xs)+                   (ExpectArity xs+                           (Either+                              (VerbListenerT z (FileExtListenerT Response m ()) m ())+                              (VerbListenerT z Response m ())))+                   (RUPTrie T.Text+                           (Either+                              (VerbListenerT z (FileExtListenerT Response m ()) m ())+                              (VerbListenerT z Response m ())))+               , Extrude (UrlChunks xs)+                   (RUPTrie T.Text+                           (Either+                              (VerbListenerT z (FileExtListenerT Response m ()) m ())+                              (VerbListenerT z Response m ())))+                   (RUPTrie T.Text+                           (Either+                              (VerbListenerT z (FileExtListenerT Response m ()) m ())+                              (VerbListenerT z Response m ())))+               ) =>+               UrlChunks xs+            -> ExpectArity xs (Either (VerbListenerT z (FileExtListenerT Response m ()) m ()) (VerbListenerT z Response m ()))+            -> [HandlerT z m ()]+            -> HandlerT z m ()+handleParse ts vl [] =+  HandlerT $ tell (singleton ts vl, mempty)+handleParse ts vl cs = do   (child,_) <- lift $ foldM (\acc c -> (acc <>) <$> (execWriterT $ runHandler c)) mempty cs -  HandlerT $ tell $-    case ts of-      [] -> case unMergeRooted child of-              Rooted _ xs -> (MergeRooted $ Rooted (Just vfrs) xs, mempty)-      _  -> let child' = push (unMergeRooted child) $ NE.fromList ts in-            (MergeRooted $ Rooted Nothing [P.assign (NE.fromList ts) (Just vfrs) child'], mempty)+  HandlerT $ tell $ let+                      child' = extrude ts child+                    in+                    (P.merge child' $ singleton ts vl, mempty)  -notFound :: Monad m =>-            [T.Text]-         -> FileExtListenerT Response m ()+notFound :: ( Monad m+            , Singleton (UrlChunks xs)+                (ExpectArity xs+                           (Either+                              (VerbListenerT z (FileExtListenerT Response m ()) m ())+                              (VerbListenerT z Response m ())))+                (RUPTrie T.Text+                           (Either+                              (VerbListenerT z (FileExtListenerT Response m ()) m ())+                              (VerbListenerT z Response m ())))+            ) =>+            UrlChunks xs+         -> ExpectArity xs (Either (VerbListenerT z (FileExtListenerT Response m ()) m ()) (VerbListenerT z Response m ()))          -> HandlerT z m ()-notFound ts flistener = do-  (fes :: FileExts Response) <- lift $ execWriterT $ runFileExtListenerT flistener-  HandlerT $ tell $-    case ts of-      [] -> (mempty, MergeRooted $ Rooted (Just fes) [])-      _  -> (mempty, MergeRooted $ Rooted Nothing    [Rest (NE.fromList ts) fes])+notFound ts vl = do+  HandlerT $ tell (mempty, singleton ts vl)   -- | Turns a @HandlerT@ into a Wai @Application@@@ -96,50 +152,120 @@       -> Request       -> (Response -> IO ResponseReceived) -> m ResponseReceived route h req respond = do-  (rtrie, festrie) <- execWriterT $ runHandler h-  let trie         = unMergeRooted rtrie-      notFoundTrie = unMergeRooted festrie-      mMethod      = httpMethodToMSym $ requestMethod req-      mFileext     = case pathInfo req of-                       [] -> Just Html-                       xs -> possibleExts $ getFileExt $ last xs-      mNotFoundMap :: Maybe (M.Map FileExt Response)-      mNotFoundMap = (return . unFileExts) =<< R.lookupNearestParent (pathInfo req) notFoundTrie+  (rtrie, nftrie) <- execWriterT $ runHandler h+  let mMethod  = httpMethodToMSym $ requestMethod req+      mFileext = case pathInfo req of+                         [] -> Just Html+                         xs -> possibleExts $ getFileExt $ last xs+      -- meitherNotFound :: Maybe (Either (VerbListenerT z (FileExtListenerT Response m ()) m ()) (VerbListenerT z Response m ()))+      meitherNotFound = P.lookupNearestParent (pathInfo req) nftrie +  notFoundBasic <- handleNotFound Html Get meitherNotFound+   case (mFileext, mMethod) of-    (Just f, Just v) -> let cleanedPathInfo = applyToLast trimFileExt $ pathInfo req-                        in-                        case R.lookup cleanedPathInfo trie of-      Just vmap -> continue f v vmap mNotFoundMap-      Nothing  -> case trimFileExt $ last $ pathInfo req of-        "index" -> case R.lookup (init $ pathInfo req) trie of-          Just vmap -> continue f v vmap mNotFoundMap-          Nothing -> liftIO $ respond $ fromMaybe plain404 $ mNotFoundMap >>= lookupMin f-        _       -> liftIO $ respond $ fromMaybe plain404 $ mNotFoundMap >>= lookupMin f-    _ -> liftIO $ respond $ fromMaybe plain404 $ mNotFoundMap >>= lookupMin Html+    (Just f, Just v) -> do+      menf <- handleNotFound f v meitherNotFound+      let cleanedPathInfo = applyToLast trimFileExt $ pathInfo req+      case P.lookup cleanedPathInfo rtrie of+        Just eitherM -> continue f v eitherM $ menf+        Nothing  -> case trimFileExt $ last $ pathInfo req of+          "index" -> case P.lookup (init $ pathInfo req) rtrie of+            Just eitherM -> continue f v eitherM $ menf+            Nothing -> liftIO $ respond404 $ menf+          _       -> liftIO $ respond404 $ menf+    _ -> liftIO $ respond404 $ notFoundBasic    where-    continue :: MonadIO m => FileExt -> Verb -> Verbs z m Response -> Maybe (M.Map FileExt Response) -> m ResponseReceived-    continue f v vmap mNotFoundMap = case M.lookup v $ unVerbs vmap of-        Just (mreqbodyf,femap) ->-          case lookupMin f $ unFileExts femap of-            Just r -> do-              case mreqbodyf of-                Nothing    -> liftIO $ respond r-                Just (reqbf,Nothing) -> do-                  body <- liftIO $ strictRequestBody req-                  (runReaderT $ reqbf) body-                  liftIO $ respond r-                Just (reqbf,Just bl) -> do-                  case requestBodyLength req of-                    KnownLength bl' -> if bl' <= bl-                                         then do body <- liftIO $ strictRequestBody req-                                                 (runReaderT $ reqbf) body-                                                 liftIO $ respond r-                                         else liftIO $ respond $ fromMaybe plain404 $ mNotFoundMap >>= lookupMin f-                    _ -> liftIO $ respond $ fromMaybe plain404 $ mNotFoundMap >>= lookupMin f-            Nothing -> liftIO $ respond $ fromMaybe plain404 $ mNotFoundMap >>= lookupMin f-        Nothing -> liftIO $ respond $ fromMaybe plain404 $ lookupMin f =<< mNotFoundMap+    handleNotFound :: MonadIO m =>+                      FileExt+                   -> Verb+                   -> Maybe ( Either (VerbListenerT z (FileExtListenerT Response m ()) m ()) (VerbListenerT z Response m ()) )+                   -> m (Maybe Response)+    handleNotFound f v meitherNotFound = case meitherNotFound of+      Just (Left litmonad) -> do+        vmapLit <- execWriterT $ runVerbListenerT litmonad+        case M.lookup v (unVerbs vmapLit) of+          Just (_, femonad) -> do+            femap <- execWriterT $ runFileExtListenerT femonad+            return $ lookupMin f $ unFileExts femap+          Nothing -> return Nothing+      Just (Right predmonad) -> do+        vmapPred <- execWriterT $ runVerbListenerT predmonad+        case M.lookup v (unVerbs vmapPred) of+          Just (_, r) -> return $ Just r+          Nothing -> return Nothing+      Nothing -> return Nothing++    respond404 mr = respond $ fromMaybe plain404 mr++    continue :: MonadIO m =>+                FileExt+             -> Verb+             -> Either (VerbListenerT z (FileExtListenerT Response m ()) m ()) (VerbListenerT z Response m ())+             -> Maybe Response+             -> m ResponseReceived+    continue f v eitherM mnfResp = case eitherM of+       Left litmonad -> do+         vmapLit <- execWriterT $ runVerbListenerT litmonad+         continueLit f v (unVerbs vmapLit) mnfResp+       Right predmonad -> do+         vmapPred <- execWriterT $ runVerbListenerT predmonad+         continuePred f v (unVerbs vmapPred) mnfResp+++    continueLit :: MonadIO m =>+                   FileExt+                -> Verb+                -> M.Map Verb (Maybe (ReaderT BL.ByteString m z, Maybe BodyLength), FileExtListenerT Response m ())+                -> Maybe Response+                -> m ResponseReceived+    continueLit f v vmap mnfResp = case M.lookup v vmap of+      Just (mreqbodyf, femonad) -> do+        femap <- execWriterT $ runFileExtListenerT femonad+        case lookupMin f $ unFileExts femap of+          Just r -> do+            case mreqbodyf of+              Nothing              -> liftIO $ respond r+              Just (reqbf,Nothing) -> do+                body <- liftIO $ strictRequestBody req+                (runReaderT $ reqbf) body+                liftIO $ respond r+              Just (reqbf,Just bl) -> do+                case requestBodyLength req of+                  KnownLength bl' -> if bl' <= bl+                                       then do body <- liftIO $ strictRequestBody req+                                               (runReaderT $ reqbf) body+                                               liftIO $ respond r+                                       else liftIO $ respond404 mnfResp+                  _ -> liftIO $ respond404 mnfResp+          Nothing -> liftIO $ respond404 mnfResp+      Nothing -> liftIO $ respond404 mnfResp+++    continuePred :: MonadIO m =>+                    FileExt+                 -> Verb+                 -> M.Map Verb (Maybe (ReaderT BL.ByteString m z, Maybe BodyLength), Response)+                 -> Maybe Response+                 -> m ResponseReceived+    continuePred f v vmap mnfResp = case M.lookup v vmap of+        Just (mreqbodyf, r) ->+          case mreqbodyf of+            Nothing              -> liftIO $ respond r+            Just (reqbf,Nothing) -> do+              body <- liftIO $ strictRequestBody req+              (runReaderT $ reqbf) body+              liftIO $ respond r+            Just (reqbf,Just bl) -> do+              case requestBodyLength req of+                KnownLength bl' -> if bl' <= bl+                                     then do body <- liftIO $ strictRequestBody req+                                             (runReaderT $ reqbf) body+                                             liftIO $ respond r+                                     else liftIO $ respond404 mnfResp+                _ -> liftIO $ respond404 mnfResp+        Nothing -> liftIO $ respond404 mnfResp+      plain404 = responseLBS status404 [("Content-Type","text/plain")] "404" 
src/Web/Routes/Nested/FileExtListener.hs view
@@ -68,6 +68,10 @@   FileExtListenerT $ tell $     FileExts $ singleton Json r +jsonOnly :: (A.ToJSON j) =>+            j -> Response+jsonOnly i =+  responseLBS status200 [("Content-Type", "application/json")] $ A.encode i  jsonp :: (A.ToJSON j, Monad m) =>          j -> FileExtListenerT Response m ()@@ -77,6 +81,10 @@   FileExtListenerT $ tell $     FileExts $ singleton Json r +jsonpOnly :: (A.ToJSON j) =>+            j -> Response+jsonpOnly i =+  responseLBS status200 [("Content-Type", "application/javascript")] $ A.encode i  text :: (Monad m) =>         LT.Text -> FileExtListenerT Response m ()@@ -86,6 +94,9 @@   FileExtListenerT $ tell $     FileExts $ singleton Text r +textOnly :: LT.Text -> Response+textOnly i =+  responseLBS status200 [("Content-Type", "text/plain")] $ LT.encodeUtf8 i  blaze :: (Monad m ) =>          H.Html -> FileExtListenerT Response m ()@@ -95,6 +106,9 @@   FileExtListenerT $ tell $     FileExts $ singleton Html r +blazeOnly :: H.Html -> Response+blazeOnly i =+  responseLBS status200 [("Content-Type", "text/html")] $ LT.encodeUtf8 $ H.renderHtml i  lucid :: (Monad m) =>          L.HtmlT m () -> FileExtListenerT Response m ()@@ -104,6 +118,11 @@   FileExtListenerT $ tell $     FileExts $ singleton Html r +lucidOnly :: (Monad m) =>+             L.HtmlT m () -> m Response+lucidOnly i = do+  i' <- L.renderBST i+  return $ responseLBS status200 [("Content-Type", "text/html")] $ i'  builder :: (Monad m) =>            BU.Builder -> RequestHeaders@@ -113,6 +132,9 @@   FileExtListenerT $ tell $     FileExts $ singleton e r +builderOnly :: BU.Builder -> RequestHeaders -> Response+builderOnly i hs =+  responseBuilder status200 hs i  bytestring :: (Monad m) =>               B.ByteString -> RequestHeaders@@ -121,3 +143,7 @@   let r = responseLBS status200 hs i in   FileExtListenerT $ tell $     FileExts $ singleton e r++bytestringOnly :: B.ByteString -> RequestHeaders -> Response+bytestringOnly i hs =+  responseLBS status200 hs i
+ src/Web/Routes/Nested/Types.hs view
@@ -0,0 +1,106 @@+{-# LANGUAGE+    GADTs+  , TypeOperators+  , TypeFamilies+  , KindSignatures+  , DataKinds+  , RankNTypes+  , FlexibleInstances+  , UndecidableInstances+  , OverlappingInstances+  , MultiParamTypeClasses+  , FunctionalDependencies+  #-}++module Web.Routes.Nested.Types+  ( Singleton (..)+  , Extend (..)+  , Extrude (..)+  , iResultToMaybe+  , restAreLits+  , ToNE (..)+  , ToL (..)+  , ExpectArity+  , module Web.Routes.Nested.Types.UrlChunks+  ) where++import Data.Attoparsec.Text+import Web.Routes.Nested.Types.UrlChunks+import Web.Routes.Nested.VerbListener+import Web.Routes.Nested.FileExtListener+import Network.Wai+import qualified Data.Text as T+import           Data.List.NonEmpty+import qualified Data.List.NonEmpty as NE+import Data.Trie.Pred.Unified+import Data.Trie.Pred.Unified.Tail+++class Singleton chunks a trie | chunks a -> trie where+  singleton :: chunks -> a -> trie++instance Singleton (UrlChunks '[]) a (RUPTrie T.Text a) where+  singleton Root r = Rooted (Just r) []++instance ( Singleton (UrlChunks xs) a trie0+         , Extend (EitherUrlChunk x) trie0 trie1 )=>+         Singleton (UrlChunks (x ': xs)) a trie1 where+  singleton (Cons u us) r = extend u (singleton us r)+++class Extend eitherUrlChunk child result | eitherUrlChunk child -> result where+  extend :: eitherUrlChunk -> child -> result++instance Extend (EitherUrlChunk  'Nothing) (RUPTrie T.Text a)        (RUPTrie T.Text a) where+  extend ((:=) t) (Rooted mx xs) = Rooted Nothing [UMore t mx xs]++instance Extend (EitherUrlChunk ('Just r)) (RUPTrie T.Text (r -> a)) (RUPTrie T.Text a) where+  extend ((:~) (t,q)) (Rooted mx xs) = Rooted Nothing [UPred t (iResultToMaybe . parse q) mx xs]+++class Extrude chunks start result | chunks start -> result where+  extrude :: chunks -> start -> result++instance Extrude (UrlChunks '[]) (RUPTrie T.Text a) (RUPTrie T.Text a) where+  extrude Root r = r++instance ( Extrude (UrlChunks xs) trie0 trie1+         , Extend (EitherUrlChunk x) trie1 trie2 ) => Extrude (UrlChunks (x ': xs)) trie0 trie2 where+  extrude (Cons u us) r = extend u (extrude us r)+++iResultToMaybe :: IResult T.Text r -> Maybe r+iResultToMaybe (Done _ r) = Just r+iResultToMaybe _          = Nothing++restAreLits :: UrlChunks xs -> Bool+restAreLits Root = False+restAreLits (Cons ((:=) _) Root) = True+restAreLits (Cons ((:=) _) us)   = restAreLits us+restAreLits (Cons _ _) = False++class ToNE chunks where+  toNE :: chunks -> NonEmpty T.Text++instance ToNE (UrlChunks ('Nothing ': '[])) where+  toNE (Cons ((:=) t) Root) = t :| []++instance ToNE (UrlChunks xs) => ToNE (UrlChunks ('Nothing ': xs)) where+  toNE (Cons ((:=) t) us) = t :| (toList $ toNE us)+++class ToL chunks where+  toL :: chunks -> [T.Text]++instance ToL (UrlChunks '[]) where+  toL Root = []++instance ToL (UrlChunks xs) => ToL (UrlChunks ('Nothing ': xs)) where+  toL (Cons ((:=) t) us) = t : toL us+++type family ExpectArity (xs :: [Maybe *]) (r :: *) :: * where+  ExpectArity '[] r = r+  ExpectArity ('Nothing  ': xs) r = ExpectArity xs r+  ExpectArity (('Just x) ': xs) r = x -> (ExpectArity xs r)+
src/Web/Routes/Nested/VerbListener.hs view
@@ -5,6 +5,8 @@ {-# LANGUAGE ScopedTypeVariables        #-} {-# LANGUAGE StandaloneDeriving         #-} {-# LANGUAGE BangPatterns               #-}+{-# LANGUAGE MultiParamTypeClasses      #-}+{-# LANGUAGE FunctionalDependencies     #-}  module Web.Routes.Nested.VerbListener where @@ -32,7 +34,7 @@  type BodyLength = Word64 -newtype Verbs z m r = Verbs { unVerbs :: Map Verb (Maybe (ReaderT BL.ByteString m z, Maybe BodyLength), FileExts r) }+newtype Verbs z m r = Verbs { unVerbs :: Map Verb (Maybe (ReaderT BL.ByteString m z, Maybe BodyLength), r) }   deriving (Functor, Traversable)  deriving instance                  Monoid    (Verbs z m a)@@ -49,67 +51,59 @@   lift ma = VerbListenerT $ lift ma  +foldMWithKey :: Monad m => (acc -> Verb -> a -> m acc) -> acc -> Map Verb a -> m acc+foldMWithKey f i map = foldlWithKey (\macc k a -> (\mer -> f mer k a) =<< macc) (return i) map++ get :: (Monad m) =>-       FileExtListenerT Response m a-    -> VerbListenerT z Response m ()-get !flistener = do-  (fileexts :: FileExts Response) <- lift $ execWriterT $-                                     runFileExtListenerT flistener-  let new = singleton Get (Nothing, fileexts)+       a+    -> VerbListenerT z a m ()+get r = do+  let new = singleton Get (Nothing, r)   VerbListenerT $ tell $ Verbs new   post :: (Monad m, MonadIO m) =>         (BL.ByteString -> m z)-     -> FileExtListenerT Response m a-     -> VerbListenerT z Response m ()-post !handle !flistener = do-  (fileexts :: FileExts Response) <- lift $ execWriterT $-                                     runFileExtListenerT flistener-  let new = singleton Post (Just $ (ReaderT handle, Nothing), fileexts)+     -> a+     -> VerbListenerT z a m ()+post handle r = do+  let new = singleton Post (Just $ (ReaderT handle, Nothing), r)   VerbListenerT $ tell $ Verbs new   postMax :: (Monad m, MonadIO m) =>            BodyLength         -> (BL.ByteString -> m z)-        -> FileExtListenerT Response m a-        -> VerbListenerT z Response m ()-postMax !bl !handle !flistener = do-  (fileexts :: FileExts Response) <- lift $ execWriterT $-                                     runFileExtListenerT flistener-  let new = singleton Post (Just $ (ReaderT handle, Just bl), fileexts)+        -> a+        -> VerbListenerT z a m ()+postMax bl handle r = do+  let new = singleton Post (Just $ (ReaderT handle, Just bl), r)   VerbListenerT $ tell $ Verbs new   put :: (Monad m, MonadIO m) =>        (BL.ByteString -> m z)-    -> FileExtListenerT Response m a-    -> VerbListenerT z Response m ()-put !handle !flistener = do-  (fileexts :: FileExts Response) <- lift $ execWriterT $-                                     runFileExtListenerT flistener-  let new = singleton Put (Just $ (ReaderT handle, Nothing), fileexts)+    -> a+    -> VerbListenerT z a m ()+put handle r = do+  let new = singleton Put (Just $ (ReaderT handle, Nothing), r)   VerbListenerT $ tell $ Verbs new   putMax :: (Monad m, MonadIO m) =>           BodyLength        -> (BL.ByteString -> m z)-       -> FileExtListenerT Response m a-       -> VerbListenerT z Response m ()-putMax !bl !handle !flistener = do-  (fileexts :: FileExts Response) <- lift $ execWriterT $-                                     runFileExtListenerT flistener-  let new = singleton Put (Just $ (ReaderT handle, Just bl), fileexts)+       -> a+       -> VerbListenerT z a m ()+putMax bl handle r = do+  let new = singleton Put (Just $ (ReaderT handle, Just bl), r)   VerbListenerT $ tell $ Verbs new   delete :: (Monad m) =>-          FileExtListenerT Response m a-       -> VerbListenerT z Response m ()-delete !flistener = do-  (fileexts :: FileExts Response) <- lift $ execWriterT $-                                     runFileExtListenerT flistener-  let new = singleton Delete (Nothing, fileexts)+          a+       -> VerbListenerT z a m ()+delete r = do+  let new = singleton Delete (Nothing, r)   VerbListenerT $ tell $ Verbs new