220 lines
6.6 KiB
Haskell
220 lines
6.6 KiB
Haskell
{-# LANGUAGE QuasiQuotes #-}
|
|
{-# LANGUAGE RecordWildCards #-}
|
|
{-# LANGUAGE TemplateHaskell #-}
|
|
{-# LANGUAGE TypeFamilies #-}
|
|
{-# LANGUAGE MultiParamTypeClasses #-}
|
|
{-# LANGUAGE TypeSynonymInstances #-}
|
|
{-# LANGUAGE FlexibleInstances #-}
|
|
{-# LANGUAGE OverloadedStrings #-}
|
|
{-# LANGUAGE ViewPatterns #-}
|
|
{-# LANGUAGE CPP #-}
|
|
module Hierarchy
|
|
( hierarchy
|
|
, Dispatcher (..)
|
|
, runHandler
|
|
, Handler2
|
|
, App
|
|
, toText
|
|
, Env (..)
|
|
, subDispatch
|
|
-- to avoid warnings
|
|
, deleteDelete2
|
|
, deleteDelete3
|
|
) where
|
|
|
|
import Test.Hspec
|
|
import Test.HUnit
|
|
import Yesod.Routes.Parse
|
|
import Yesod.Routes.TH
|
|
import Yesod.Routes.Class
|
|
import Language.Haskell.TH.Syntax
|
|
import Data.Text (Text, pack, unpack, append)
|
|
import Data.ByteString (ByteString)
|
|
import qualified Data.ByteString.Char8 as S8
|
|
import qualified Data.Set as Set
|
|
|
|
class ToText a where
|
|
toText :: a -> Text
|
|
|
|
instance ToText Text where toText = id
|
|
instance ToText String where toText = pack
|
|
|
|
type Handler2 sub master a = a
|
|
type Handler site a = Handler2 site site a
|
|
|
|
type Request = ([Text], ByteString) -- path info, method
|
|
type App sub master = Request -> (Text, Maybe (Route master))
|
|
data Env sub master = Env
|
|
{ envToMaster :: Route sub -> Route master
|
|
, envSub :: sub
|
|
, envMaster :: master
|
|
}
|
|
|
|
subDispatch
|
|
:: (Env sub master -> App sub master)
|
|
-> (Handler2 sub master Text -> Env sub master -> Maybe (Route sub) -> App sub master)
|
|
-> (master -> sub)
|
|
-> (Route sub -> Route master)
|
|
-> Env master master
|
|
-> App sub master
|
|
subDispatch handler _runHandler getSub toMaster env req =
|
|
handler env' req
|
|
where
|
|
env' = env
|
|
{ envToMaster = envToMaster env . toMaster
|
|
, envSub = getSub $ envMaster env
|
|
}
|
|
|
|
class Dispatcher sub master where
|
|
dispatcher :: Env sub master -> App sub master
|
|
|
|
runHandler
|
|
:: ToText a
|
|
=> Handler2 sub master a
|
|
-> Env sub master
|
|
-> Maybe (Route sub)
|
|
-> App sub master
|
|
runHandler h Env {..} route _ = (toText h, fmap envToMaster route)
|
|
|
|
data Hierarchy = Hierarchy
|
|
|
|
do
|
|
let resources = [parseRoutes|
|
|
/ HomeR GET
|
|
|
|
----------------------------------------
|
|
|
|
/!#Int BackwardsR GET
|
|
|
|
/admin/#Int AdminR:
|
|
/ AdminRootR GET
|
|
/login LoginR GET POST
|
|
/table/#Text TableR GET
|
|
|
|
/nest/ NestR !NestingAttr:
|
|
|
|
/spaces SpacedR GET !NonNested
|
|
|
|
/nest2 Nest2:
|
|
/ GetPostR GET POST
|
|
/get Get2 GET
|
|
/post Post2 POST
|
|
-- /#Int Delete2 DELETE
|
|
/nest3 Nest3:
|
|
/get Get3 GET
|
|
/post Post3 POST
|
|
-- /#Int Delete3 DELETE
|
|
|
|
/afterwards AfterR !parent !key=value1:
|
|
/ After GET !child !key=value2
|
|
|
|
-- /trailing-nest TrailingNestR:
|
|
-- /foo TrailingFooR GET
|
|
-- /#Int TrailingIntR GET
|
|
|]
|
|
|
|
rrinst <- mkRenderRouteInstance [] (ConT ''Hierarchy) $ map (fmap parseType) resources
|
|
rainst <- mkRouteAttrsInstance [] (ConT ''Hierarchy) $ map (fmap parseType) resources
|
|
prinst <- mkParseRouteInstance [] (ConT ''Hierarchy) $ map (fmap parseType) resources
|
|
dispatch <- mkDispatchClause MkDispatchSettings
|
|
{ mdsRunHandler = [|runHandler|]
|
|
, mdsSubDispatcher = [|subDispatch|]
|
|
, mdsGetPathInfo = [|fst|]
|
|
, mdsMethod = [|snd|]
|
|
, mdsSetPathInfo = [|\p (_, m) -> (p, m)|]
|
|
, mds404 = [|pack "404"|]
|
|
, mds405 = [|pack "405"|]
|
|
, mdsGetHandler = defaultGetHandler
|
|
, mdsUnwrapper = return
|
|
} resources
|
|
return
|
|
#if MIN_VERSION_template_haskell(2,11,0)
|
|
$ InstanceD Nothing
|
|
#else
|
|
$ InstanceD
|
|
#endif
|
|
[]
|
|
(ConT ''Dispatcher
|
|
`AppT` ConT ''Hierarchy
|
|
`AppT` ConT ''Hierarchy)
|
|
[FunD (mkName "dispatcher") [dispatch]]
|
|
: prinst
|
|
: rainst
|
|
: rrinst
|
|
|
|
getSpacedR :: Handler site String
|
|
getSpacedR = "root-leaf"
|
|
|
|
getGet2 :: Handler site String; getGet2 = "get"
|
|
postPost2 :: Handler site String; postPost2 = "post"
|
|
deleteDelete2 :: Int -> Handler site String; deleteDelete2 = const "delete"
|
|
getGet3 :: Handler site String; getGet3 = "get"
|
|
postPost3 :: Handler site String; postPost3 = "post"
|
|
deleteDelete3 :: Int -> Handler site String; deleteDelete3 = const "delete"
|
|
|
|
getAfter :: Handler site String; getAfter = "after"
|
|
|
|
getHomeR :: Handler site String
|
|
getHomeR = "home"
|
|
|
|
getBackwardsR :: Int -> Handler site Text
|
|
getBackwardsR _ = pack "backwards"
|
|
|
|
getAdminRootR :: Int -> Handler site Text
|
|
getAdminRootR i = pack $ "admin root: " ++ show i
|
|
|
|
getLoginR :: Int -> Handler site Text
|
|
getLoginR i = pack $ "login: " ++ show i
|
|
|
|
postLoginR :: Int -> Handler site Text
|
|
postLoginR i = pack $ "post login: " ++ show i
|
|
|
|
getTableR :: Int -> Text -> Handler site Text
|
|
getTableR _ = append "TableR "
|
|
|
|
getGetPostR :: Handler site Text
|
|
getGetPostR = pack "get"
|
|
|
|
postGetPostR :: Handler site Text
|
|
postGetPostR = pack "post"
|
|
|
|
|
|
hierarchy :: Spec
|
|
hierarchy = describe "hierarchy" $ do
|
|
it "nested with spacing" $
|
|
renderRoute (NestR SpacedR) @?= (["nest", "spaces"], [])
|
|
it "renders root correctly" $
|
|
renderRoute (AdminR 5 AdminRootR) @?= (["admin", "5"], [])
|
|
it "renders table correctly" $
|
|
renderRoute (AdminR 6 $ TableR "foo") @?= (["admin", "6", "table", "foo"], [])
|
|
let disp m ps = dispatcher
|
|
(Env
|
|
{ envToMaster = id
|
|
, envMaster = Hierarchy
|
|
, envSub = Hierarchy
|
|
})
|
|
(map pack ps, S8.pack m)
|
|
|
|
let testGetPost route getRes postRes = do
|
|
let routeStrs = map unpack $ fst (renderRoute route)
|
|
disp "GET" routeStrs @?= (getRes, Just route)
|
|
disp "POST" routeStrs @?= (postRes, Just route)
|
|
|
|
it "dispatches routes with multiple METHODs: admin" $
|
|
testGetPost (AdminR 1 LoginR) "login: 1" "post login: 1"
|
|
|
|
it "dispatches routes with multiple METHODs: nesting" $
|
|
testGetPost (NestR $ Nest2 GetPostR) "get" "post"
|
|
|
|
it "dispatches root correctly" $ disp "GET" ["admin", "7"] @?= ("admin root: 7", Just $ AdminR 7 AdminRootR)
|
|
it "dispatches table correctly" $ disp "GET" ["admin", "8", "table", "bar"] @?= ("TableR bar", Just $ AdminR 8 $ TableR "bar")
|
|
it "parses" $ do
|
|
parseRoute ([], []) @?= Just HomeR
|
|
parseRoute ([], [("foo", "bar")]) @?= Just HomeR
|
|
parseRoute (["admin", "5"], []) @?= Just (AdminR 5 AdminRootR)
|
|
parseRoute (["admin!", "5"], []) @?= (Nothing :: Maybe (Route Hierarchy))
|
|
it "inherited attributes" $ do
|
|
routeAttrs (NestR SpacedR) @?= Set.fromList ["NestingAttr", "NonNested"]
|
|
it "pair attributes" $
|
|
routeAttrs (AfterR After) @?= Set.fromList ["parent", "child", "key=value2"]
|