RouteAttrs (fixes #531)

This commit is contained in:
Michael Snoyman 2013-04-21 17:13:41 +03:00
parent 4cde171285
commit 16260f3e37
8 changed files with 83 additions and 4 deletions

View File

@ -11,6 +11,7 @@ module Yesod.Core
, YesodSubDispatch (..) , YesodSubDispatch (..)
, RenderRoute (..) , RenderRoute (..)
, ParseRoute (..) , ParseRoute (..)
, RouteAttrs (..)
-- ** Breadcrumbs -- ** Breadcrumbs
, YesodBreadcrumbs (..) , YesodBreadcrumbs (..)
, breadcrumbs , breadcrumbs

View File

@ -66,6 +66,7 @@ mkYesodGeneral :: String -- ^ foundation type
-> Q([Dec],[Dec]) -> Q([Dec],[Dec])
mkYesodGeneral name args isSub resS = do mkYesodGeneral name args isSub resS = do
renderRouteDec <- mkRenderRouteInstance site res renderRouteDec <- mkRenderRouteInstance site res
routeAttrsDec <- mkRouteAttrsInstance site res
dispatchDec <- mkDispatchInstance site res dispatchDec <- mkDispatchInstance site res
parse <- mkParseRouteInstance site res parse <- mkParseRouteInstance site res
let rname = mkName $ "resources" ++ name let rname = mkName $ "resources" ++ name
@ -77,6 +78,7 @@ mkYesodGeneral name args isSub resS = do
let dataDec = concat let dataDec = concat
[ [parse] [ [parse]
, renderRouteDec , renderRouteDec
, [routeAttrsDec]
, resourcesDec , resourcesDec
, if isSub then [] else masterTypeSyns site , if isSub then [] else masterTypeSyns site
] ]

View File

@ -12,12 +12,15 @@ import Data.Text (Text)
import Data.Maybe (fromJust) import Data.Maybe (fromJust)
import Data.Monoid (Endo (..)) import Data.Monoid (Endo (..))
import qualified Control.Monad.Trans.Writer as Writer import qualified Control.Monad.Trans.Writer as Writer
import qualified Data.Set as Set
data App = App data App = App
mkYesod "App" [parseRoutes| mkYesod "App" [parseRoutes|
/ HomeR GET / HomeR GET !home
/json JsonR GET /json JsonR GET
/parent/#Int ParentR:
/#Text/child ChildR !child
|] |]
instance Yesod App instance Yesod App
@ -40,6 +43,9 @@ getJsonR = selectRep $ do
rep typeHtml "HTML" rep typeHtml "HTML"
provideRep $ return $ object ["message" .= ("Invalid Login" :: Text)] provideRep $ return $ object ["message" .= ("Invalid Login" :: Text)]
handleChildR :: Int -> Text -> Handler ()
handleChildR _ _ = return ()
testRequest :: Int -- ^ http status code testRequest :: Int -- ^ http status code
-> Request -> Request
-> ByteString -- ^ expected body -> ByteString -- ^ expected body
@ -63,7 +69,8 @@ acceptRequest accept = defaultRequest
} }
specs :: Spec specs :: Spec
specs = describe "selectRep" $ do specs = do
describe "selectRep" $ do
test "application/json" "JSON" test "application/json" "JSON"
test (S8.unpack typeJson) "JSON" test (S8.unpack typeJson) "JSON"
test "text/xml" "XML" test "text/xml" "XML"
@ -77,3 +84,7 @@ specs = describe "selectRep" $ do
testRequest 406 (acceptRequest "text/foo") "no match found for accept header" testRequest 406 (acceptRequest "text/foo") "no match found for accept header"
test "text/*" "HTML" test "text/*" "HTML"
test "*/*" "HTML" test "*/*" "HTML"
describe "routeAttrs" $ do
it "HomeR" $ routeAttrs HomeR `shouldBe` Set.singleton "home"
it "JsonR" $ routeAttrs JsonR `shouldBe` Set.empty
it "ChildR" $ routeAttrs (ParentR 5 $ ChildR "ignored") `shouldBe` Set.singleton "child"

View File

@ -3,9 +3,11 @@
module Yesod.Routes.Class module Yesod.Routes.Class
( RenderRoute (..) ( RenderRoute (..)
, ParseRoute (..) , ParseRoute (..)
, RouteAttrs (..)
) where ) where
import Data.Text (Text) import Data.Text (Text)
import Data.Set (Set)
class Eq (Route a) => RenderRoute a where class Eq (Route a) => RenderRoute a where
-- | The type-safe URLs associated with a site argument. -- | The type-safe URLs associated with a site argument.
@ -14,3 +16,6 @@ class Eq (Route a) => RenderRoute a where
class RenderRoute a => ParseRoute a where class RenderRoute a => ParseRoute a where
parseRoute :: ([Text], [(Text, Text)]) -> Maybe (Route a) parseRoute :: ([Text], [(Text, Text)]) -> Maybe (Route a)
class RenderRoute a => RouteAttrs a where
routeAttrs :: Route a -> Set Text

View File

@ -4,6 +4,7 @@ module Yesod.Routes.TH
-- * Functions -- * Functions
, module Yesod.Routes.TH.RenderRoute , module Yesod.Routes.TH.RenderRoute
, module Yesod.Routes.TH.ParseRoute , module Yesod.Routes.TH.ParseRoute
, module Yesod.Routes.TH.RouteAttrs
-- ** Dispatch -- ** Dispatch
, module Yesod.Routes.TH.Dispatch , module Yesod.Routes.TH.Dispatch
) where ) where
@ -11,4 +12,5 @@ module Yesod.Routes.TH
import Yesod.Routes.TH.Types import Yesod.Routes.TH.Types
import Yesod.Routes.TH.RenderRoute import Yesod.Routes.TH.RenderRoute
import Yesod.Routes.TH.ParseRoute import Yesod.Routes.TH.ParseRoute
import Yesod.Routes.TH.RouteAttrs
import Yesod.Routes.TH.Dispatch import Yesod.Routes.TH.Dispatch

View File

@ -0,0 +1,38 @@
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE RecordWildCards #-}
module Yesod.Routes.TH.RouteAttrs
( mkRouteAttrsInstance
) where
import Yesod.Routes.TH.Types
import Yesod.Routes.Class
import Language.Haskell.TH.Syntax
import Data.Set (fromList)
import Data.Text (pack)
mkRouteAttrsInstance :: Type -> [ResourceTree a] -> Q Dec
mkRouteAttrsInstance typ ress = do
clauses <- mapM (goTree id) ress
return $ InstanceD [] (ConT ''RouteAttrs `AppT` typ)
[ FunD 'routeAttrs $ concat clauses
]
goTree :: (Pat -> Pat) -> ResourceTree a -> Q [Clause]
goTree front (ResourceLeaf res) = fmap return $ goRes front res
goTree front (ResourceParent name pieces trees) =
fmap concat $ mapM (goTree front') trees
where
ignored = ((replicate toIgnore WildP ++) . return)
toIgnore = length $ filter (isDynamic . snd) pieces
isDynamic Dynamic{} = True
isDynamic Static{} = False
front' = front . ConP (mkName name) . ignored
goRes :: (Pat -> Pat) -> Resource a -> Q Clause
goRes front Resource {..} =
return $ Clause
[front $ RecP (mkName resourceName) []]
(NormalB $ VarE 'fromList `AppE` ListE (map toText resourceAttrs))
[]
where
toText s = VarE 'pack `AppE` LitE (StringL s)

View File

@ -23,6 +23,7 @@ import Yesod.Routes.TH hiding (Dispatch)
import Language.Haskell.TH.Syntax import Language.Haskell.TH.Syntax
import Hierarchy import Hierarchy
import qualified Data.ByteString.Char8 as S8 import qualified Data.ByteString.Char8 as S8
import qualified Data.Set as Set
result :: ([Text] -> Maybe Int) -> Dispatch Int result :: ([Text] -> Maybe Int) -> Dispatch Int
result f ts = f ts result f ts = f ts
@ -105,15 +106,24 @@ getMySubParam _ = MySubParam
do do
texts <- [t|[Text]|] texts <- [t|[Text]|]
let ress = map ResourceLeaf let resLeaves = map ResourceLeaf
[ Resource "RootR" [] (Methods Nothing ["GET"]) [] [ Resource "RootR" [] (Methods Nothing ["GET"]) ["foo", "bar"]
, Resource "BlogPostR" (addCheck [Static "blog", Dynamic $ ConT ''Text]) (Methods Nothing ["GET", "POST"]) [] , Resource "BlogPostR" (addCheck [Static "blog", Dynamic $ ConT ''Text]) (Methods Nothing ["GET", "POST"]) []
, Resource "WikiR" (addCheck [Static "wiki"]) (Methods (Just texts) []) [] , Resource "WikiR" (addCheck [Static "wiki"]) (Methods (Just texts) []) []
, Resource "SubsiteR" (addCheck [Static "subsite"]) (Subsite (ConT ''MySub) "getMySub") [] , Resource "SubsiteR" (addCheck [Static "subsite"]) (Subsite (ConT ''MySub) "getMySub") []
, Resource "SubparamR" (addCheck [Static "subparam", Dynamic $ ConT ''Int]) (Subsite (ConT ''MySubParam) "getMySubParam") [] , Resource "SubparamR" (addCheck [Static "subparam", Dynamic $ ConT ''Int]) (Subsite (ConT ''MySubParam) "getMySubParam") []
] ]
resParent = ResourceParent
"ParentR"
[ (True, Static "foo")
, (True, Dynamic $ ConT ''Text)
]
[ ResourceLeaf $ Resource "ChildR" [] (Methods Nothing ["GET"]) ["child"]
]
ress = resParent : resLeaves
addCheck = map ((,) True) addCheck = map ((,) True)
rrinst <- mkRenderRouteInstance (ConT ''MyApp) ress rrinst <- mkRenderRouteInstance (ConT ''MyApp) ress
rainst <- mkRouteAttrsInstance (ConT ''MyApp) ress
prinst <- mkParseRouteInstance (ConT ''MyApp) ress prinst <- mkParseRouteInstance (ConT ''MyApp) ress
dispatch <- mkDispatchClause MkDispatchSettings dispatch <- mkDispatchClause MkDispatchSettings
{ mdsRunHandler = [|runHandler|] { mdsRunHandler = [|runHandler|]
@ -133,6 +143,7 @@ do
`AppT` ConT ''MyApp) `AppT` ConT ''MyApp)
[FunD (mkName "dispatcher") [dispatch]] [FunD (mkName "dispatcher") [dispatch]]
: prinst : prinst
: rainst
: rrinst : rrinst
instance Dispatcher MySub master where instance Dispatcher MySub master where
@ -336,6 +347,11 @@ main = hspec $ do
/bar/baz Foo3 /bar/baz Foo3
|] |]
findOverlapNames routes @?= [] findOverlapNames routes @?= []
describe "routeAttrs" $ do
it "works" $ do
routeAttrs RootR @?= Set.fromList [pack "foo", pack "bar"]
it "hierarchy" $ do
routeAttrs (ParentR (pack "ignored") ChildR) @?= Set.singleton (pack "child")
hierarchy hierarchy
getRootR :: Text getRootR :: Text
@ -349,3 +365,6 @@ postBlogPostR t = pack $ "POST some blog post: " ++ unpack t
handleWikiR :: [Text] -> String handleWikiR :: [Text] -> String
handleWikiR ts = "the wiki: " ++ show ts handleWikiR ts = "the wiki: " ++ show ts
getChildR :: Text -> Text
getChildR = id

View File

@ -30,6 +30,7 @@ library
other-modules: Yesod.Routes.TH.Dispatch other-modules: Yesod.Routes.TH.Dispatch
Yesod.Routes.TH.RenderRoute Yesod.Routes.TH.RenderRoute
Yesod.Routes.TH.ParseRoute Yesod.Routes.TH.ParseRoute
Yesod.Routes.TH.RouteAttrs
Yesod.Routes.TH.Types Yesod.Routes.TH.Types
ghc-options: -Wall ghc-options: -Wall