RouteSpec.hs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342
  1. {-# LANGUAGE TemplateHaskell #-}
  2. {-# LANGUAGE ViewPatterns#-}
  3. {-# LANGUAGE RecordWildCards #-}
  4. {-# LANGUAGE TypeFamilies #-}
  5. {-# LANGUAGE FlexibleInstances #-}
  6. {-# LANGUAGE ExistentialQuantification #-}
  7. {-# LANGUAGE MultiParamTypeClasses #-}
  8. {-# LANGUAGE RankNTypes #-}
  9. {-# LANGUAGE FunctionalDependencies #-}
  10. {-# LANGUAGE TypeSynonymInstances #-}
  11. {-# LANGUAGE QuasiQuotes #-}
  12. {-# LANGUAGE CPP #-}
  13. {-# LANGUAGE OverloadedStrings #-}
  14. {-# OPTIONS_GHC -ddump-splices #-}
  15. import Test.Hspec
  16. import Test.HUnit ((@?=))
  17. import Data.Text (Text, pack, unpack, singleton)
  18. import Yesod.Routes.Class hiding (Route)
  19. import qualified Yesod.Routes.Class as YRC
  20. import Yesod.Routes.Parse (parseRoutesNoCheck, parseTypeTree, TypeTree (..))
  21. import Yesod.Routes.Overlap (findOverlapNames)
  22. import Yesod.Routes.TH hiding (Dispatch)
  23. import Language.Haskell.TH.Syntax
  24. import Hierarchy
  25. import qualified Data.ByteString.Char8 as S8
  26. import qualified Data.Set as Set
  27. data MyApp = MyApp
  28. data MySub = MySub
  29. instance RenderRoute MySub where
  30. data
  31. #if MIN_VERSION_base(4,5,0)
  32. Route
  33. #else
  34. YRC.Route
  35. #endif
  36. MySub = MySubRoute ([Text], [(Text, Text)])
  37. deriving (Show, Eq, Read)
  38. renderRoute (MySubRoute x) = x
  39. instance ParseRoute MySub where
  40. parseRoute = Just . MySubRoute
  41. getMySub :: MyApp -> MySub
  42. getMySub MyApp = MySub
  43. data MySubParam = MySubParam Int
  44. instance RenderRoute MySubParam where
  45. data
  46. #if MIN_VERSION_base(4,5,0)
  47. Route
  48. #else
  49. YRC.Route
  50. #endif
  51. MySubParam = ParamRoute Char
  52. deriving (Show, Eq, Read)
  53. renderRoute (ParamRoute x) = ([singleton x], [])
  54. instance ParseRoute MySubParam where
  55. parseRoute ([unpack -> [x]], _) = Just $ ParamRoute x
  56. parseRoute _ = Nothing
  57. getMySubParam :: MyApp -> Int -> MySubParam
  58. getMySubParam _ = MySubParam
  59. do
  60. texts <- [t|[Text]|]
  61. let resLeaves = map ResourceLeaf
  62. [ Resource "RootR" [] (Methods Nothing ["GET"]) ["foo", "bar"] True
  63. , Resource "BlogPostR" [Static "blog", Dynamic $ ConT ''Text] (Methods Nothing ["GET", "POST"]) [] True
  64. , Resource "WikiR" [Static "wiki"] (Methods (Just texts) []) [] True
  65. , Resource "SubsiteR" [Static "subsite"] (Subsite (ConT ''MySub) "getMySub") [] True
  66. , Resource "SubparamR" [Static "subparam", Dynamic $ ConT ''Int] (Subsite (ConT ''MySubParam) "getMySubParam") [] True
  67. ]
  68. resParent = ResourceParent
  69. "ParentR"
  70. True
  71. [ Static "foo"
  72. , Dynamic $ ConT ''Text
  73. ]
  74. [ ResourceLeaf $ Resource "ChildR" [] (Methods Nothing ["GET"]) ["child"] True
  75. ]
  76. ress = resParent : resLeaves
  77. rrinst <- mkRenderRouteInstance (ConT ''MyApp) ress
  78. rainst <- mkRouteAttrsInstance (ConT ''MyApp) ress
  79. prinst <- mkParseRouteInstance (ConT ''MyApp) ress
  80. dispatch <- mkDispatchClause MkDispatchSettings
  81. { mdsRunHandler = [|runHandler|]
  82. , mdsSubDispatcher = [|subDispatch dispatcher|]
  83. , mdsGetPathInfo = [|fst|]
  84. , mdsMethod = [|snd|]
  85. , mdsSetPathInfo = [|\p (_, m) -> (p, m)|]
  86. , mds404 = [|pack "404"|]
  87. , mds405 = [|pack "405"|]
  88. , mdsGetHandler = defaultGetHandler
  89. , mdsUnwrapper = return
  90. } ress
  91. return
  92. #if MIN_VERSION_template_haskell(2,11,0)
  93. $ InstanceD Nothing
  94. #else
  95. $ InstanceD
  96. #endif
  97. []
  98. (ConT ''Dispatcher
  99. `AppT` ConT ''MyApp
  100. `AppT` ConT ''MyApp)
  101. [FunD (mkName "dispatcher") [dispatch]]
  102. : prinst
  103. : rainst
  104. : rrinst
  105. instance Dispatcher MySub master where
  106. dispatcher env (pieces, _method) =
  107. ( pack $ "subsite: " ++ show pieces
  108. , Just $ envToMaster env route
  109. )
  110. where
  111. route = MySubRoute (pieces, [])
  112. instance Dispatcher MySubParam master where
  113. dispatcher env (pieces, method) =
  114. case map unpack pieces of
  115. [[c]] ->
  116. let route = ParamRoute c
  117. toMaster = envToMaster env
  118. MySubParam i = envSub env
  119. in ( pack $ "subparam " ++ show i ++ ' ' : [c]
  120. , Just $ toMaster route
  121. )
  122. _ -> (pack "404", Nothing)
  123. {-
  124. thDispatchAlias
  125. :: (master ~ MyApp, sub ~ MyApp, handler ~ String, app ~ (String, Maybe (YRC.Route MyApp)))
  126. => master
  127. -> sub
  128. -> (YRC.Route sub -> YRC.Route master)
  129. -> app -- ^ 404 page
  130. -> handler -- ^ 405 page
  131. -> Text -- ^ method
  132. -> [Text]
  133. -> app
  134. --thDispatchAlias = thDispatch
  135. thDispatchAlias master sub toMaster app404 handler405 method0 pieces0 =
  136. case dispatch pieces0 of
  137. Just f -> f master sub toMaster app404 handler405 method0
  138. Nothing -> app404
  139. where
  140. dispatch = toDispatch
  141. [ Route [] False $ \pieces ->
  142. case pieces of
  143. [] -> do
  144. Just $ \master' sub' toMaster' _app404' handler405' method ->
  145. let handler =
  146. case Map.lookup method methodsRootR of
  147. Just f -> f
  148. Nothing -> handler405'
  149. in runHandler handler master' sub' RootR toMaster'
  150. _ -> error "Invariant violated"
  151. , Route [D.Static "blog", D.Dynamic] False $ \pieces ->
  152. case pieces of
  153. [_, x2] -> do
  154. y2 <- fromPathPiece x2
  155. Just $ \master' sub' toMaster' _app404' handler405' method ->
  156. let handler =
  157. case Map.lookup method methodsBlogPostR of
  158. Just f -> f y2
  159. Nothing -> handler405'
  160. in runHandler handler master' sub' (BlogPostR y2) toMaster'
  161. _ -> error "Invariant violated"
  162. , Route [D.Static "wiki"] True $ \pieces ->
  163. case pieces of
  164. _:x2 -> do
  165. y2 <- fromPathMultiPiece x2
  166. Just $ \master' sub' toMaster' _app404' _handler405' _method ->
  167. let handler = handleWikiR y2
  168. in runHandler handler master' sub' (WikiR y2) toMaster'
  169. _ -> error "Invariant violated"
  170. , Route [D.Static "subsite"] True $ \pieces ->
  171. case pieces of
  172. _:x2 -> do
  173. Just $ \master' sub' toMaster' app404' handler405' method ->
  174. dispatcher master' (getMySub sub') (toMaster' . SubsiteR) app404' handler405' method x2
  175. _ -> error "Invariant violated"
  176. , Route [D.Static "subparam", D.Dynamic] True $ \pieces ->
  177. case pieces of
  178. _:x2:x3 -> do
  179. y2 <- fromPathPiece x2
  180. Just $ \master' sub' toMaster' app404' handler405' method ->
  181. dispatcher master' (getMySubParam sub' y2) (toMaster' . SubparamR y2) app404' handler405' method x3
  182. _ -> error "Invariant violated"
  183. ]
  184. methodsRootR = Map.fromList [("GET", getRootR)]
  185. methodsBlogPostR = Map.fromList [("GET", getBlogPostR), ("POST", postBlogPostR)]
  186. -}
  187. main :: IO ()
  188. main = hspec $ do
  189. describe "RenderRoute instance" $ do
  190. it "renders root correctly" $ renderRoute RootR @?= ([], [])
  191. it "renders blog post correctly" $ renderRoute (BlogPostR $ pack "foo") @?= (map pack ["blog", "foo"], [])
  192. it "renders wiki correctly" $ renderRoute (WikiR $ map pack ["foo", "bar"]) @?= (map pack ["wiki", "foo", "bar"], [])
  193. it "renders subsite correctly" $ renderRoute (SubsiteR $ MySubRoute (map pack ["foo", "bar"], [(pack "baz", pack "bin")]))
  194. @?= (map pack ["subsite", "foo", "bar"], [(pack "baz", pack "bin")])
  195. it "renders subsite param correctly" $ renderRoute (SubparamR 6 $ ParamRoute 'c')
  196. @?= (map pack ["subparam", "6", "c"], [])
  197. describe "thDispatch" $ do
  198. let disp m ps = dispatcher
  199. (Env
  200. { envToMaster = id
  201. , envMaster = MyApp
  202. , envSub = MyApp
  203. })
  204. (map pack ps, S8.pack m)
  205. it "routes to root" $ disp "GET" [] @?= (pack "this is the root", Just RootR)
  206. it "POST root is 405" $ disp "POST" [] @?= (pack "405", Just RootR)
  207. it "invalid page is a 404" $ disp "GET" ["not-found"] @?= (pack "404", Nothing :: Maybe (YRC.Route MyApp))
  208. it "routes to blog post" $ disp "GET" ["blog", "somepost"]
  209. @?= (pack "some blog post: somepost", Just $ BlogPostR $ pack "somepost")
  210. it "routes to blog post, POST method" $ disp "POST" ["blog", "somepost2"]
  211. @?= (pack "POST some blog post: somepost2", Just $ BlogPostR $ pack "somepost2")
  212. it "routes to wiki" $ disp "DELETE" ["wiki", "foo", "bar"]
  213. @?= (pack "the wiki: [\"foo\",\"bar\"]", Just $ WikiR $ map pack ["foo", "bar"])
  214. it "routes to subsite" $ disp "PUT" ["subsite", "baz"]
  215. @?= (pack "subsite: [\"baz\"]", Just $ SubsiteR $ MySubRoute ([pack "baz"], []))
  216. it "routes to subparam" $ disp "PUT" ["subparam", "6", "q"]
  217. @?= (pack "subparam 6 q", Just $ SubparamR 6 $ ParamRoute 'q')
  218. describe "parsing" $ do
  219. it "subsites work" $ do
  220. parseRoute ([pack "subsite", pack "foo"], [(pack "bar", pack "baz")]) @?=
  221. Just (SubsiteR $ MySubRoute ([pack "foo"], [(pack "bar", pack "baz")]))
  222. describe "overlap checking" $ do
  223. it "catches overlapping statics" $ do
  224. let routes = [parseRoutesNoCheck|
  225. /foo Foo1
  226. /foo Foo2
  227. |]
  228. findOverlapNames routes @?= [("Foo1", "Foo2")]
  229. it "catches overlapping dynamics" $ do
  230. let routes = [parseRoutesNoCheck|
  231. /#Int Foo1
  232. /#String Foo2
  233. |]
  234. findOverlapNames routes @?= [("Foo1", "Foo2")]
  235. it "catches overlapping statics and dynamics" $ do
  236. let routes = [parseRoutesNoCheck|
  237. /foo Foo1
  238. /#String Foo2
  239. |]
  240. findOverlapNames routes @?= [("Foo1", "Foo2")]
  241. it "catches overlapping multi" $ do
  242. let routes = [parseRoutesNoCheck|
  243. /foo Foo1
  244. /##*Strings Foo2
  245. |]
  246. findOverlapNames routes @?= [("Foo1", "Foo2")]
  247. it "catches overlapping subsite" $ do
  248. let routes = [parseRoutesNoCheck|
  249. /foo Foo1
  250. /foo Foo2 Subsite getSubsite
  251. |]
  252. findOverlapNames routes @?= [("Foo1", "Foo2")]
  253. it "no false positives" $ do
  254. let routes = [parseRoutesNoCheck|
  255. /foo Foo1
  256. /bar/#String Foo2
  257. |]
  258. findOverlapNames routes @?= []
  259. it "obeys ignore rules" $ do
  260. let routes = [parseRoutesNoCheck|
  261. /foo Foo1
  262. /#!String Foo2
  263. /!foo Foo3
  264. |]
  265. findOverlapNames routes @?= []
  266. it "obeys multipiece ignore rules #779" $ do
  267. let routes = [parseRoutesNoCheck|
  268. /foo Foo1
  269. /+![String] Foo2
  270. |]
  271. findOverlapNames routes @?= []
  272. it "ignore rules for entire route #779" $ do
  273. let routes = [parseRoutesNoCheck|
  274. /foo Foo1
  275. !/+[String] Foo2
  276. !/#String Foo3
  277. !/foo Foo4
  278. |]
  279. findOverlapNames routes @?= []
  280. it "ignore rules for hierarchy" $ do
  281. let routes = [parseRoutesNoCheck|
  282. /+[String] Foo1
  283. !/foo Foo2:
  284. /foo Foo3
  285. /foo Foo4:
  286. /!#foo Foo5
  287. |]
  288. findOverlapNames routes @?= []
  289. it "proper boolean logic" $ do
  290. let routes = [parseRoutesNoCheck|
  291. /foo/bar Foo1
  292. /foo/baz Foo2
  293. /bar/baz Foo3
  294. |]
  295. findOverlapNames routes @?= []
  296. describe "routeAttrs" $ do
  297. it "works" $ do
  298. routeAttrs RootR @?= Set.fromList [pack "foo", pack "bar"]
  299. it "hierarchy" $ do
  300. routeAttrs (ParentR (pack "ignored") ChildR) @?= Set.singleton (pack "child")
  301. hierarchy
  302. describe "parseRouteTyoe" $ do
  303. let success s t = it s $ parseTypeTree s @?= Just t
  304. failure s = it s $ parseTypeTree s @?= Nothing
  305. success "Int" $ TTTerm "Int"
  306. success "(Int)" $ TTTerm "Int"
  307. failure "(Int"
  308. failure "(Int))"
  309. failure "[Int"
  310. failure "[Int]]"
  311. success "[Int]" $ TTList $ TTTerm "Int"
  312. success "Foo-Bar" $ TTApp (TTTerm "Foo") (TTTerm "Bar")
  313. success "Foo-Bar-Baz" $ TTApp (TTTerm "Foo") (TTTerm "Bar") `TTApp` TTTerm "Baz"
  314. getRootR :: Text
  315. getRootR = pack "this is the root"
  316. getBlogPostR :: Text -> String
  317. getBlogPostR t = "some blog post: " ++ unpack t
  318. postBlogPostR :: Text -> Text
  319. postBlogPostR t = pack $ "POST some blog post: " ++ unpack t
  320. handleWikiR :: [Text] -> String
  321. handleWikiR ts = "the wiki: " ++ show ts
  322. getChildR :: Text -> Text
  323. getChildR = id