{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TypeFamilies #-} -- | -- This module provides a ready to use implementation of `web-routes` for Snap. -- -- The tutorial assumes you have a standard Snap app with an Application.hs and -- Site.hs. -- -- To get going, you'll need to add a few things to Application.hs -- -- @ -- -- Enable a few extensions -- -- \-\- Needed to derive a generic instance for our URL data type -- \{\-\# LANGUAGE DeriveGeneric \#\-\} -- -- \-\- Needed by web-routes -- \{\-\# LANGUAGE FlexibleInstances \#\-\} -- \{\-\# LANGUAGE TypeFamilies \#\-\} -- -- \-\- Paths and params are of type Text. -- import Data.Text (Text) -- -- \-\- Snap.Web.Routes.Types exports everything you need to -- \-\- define your PathInfo and MonadRoute instances. -- import Snap.Web.Routes.Types -- -- \-\- Your URL data type. Deriving a `Generic` instance gives -- \-\- you a `PathInfo` instance for free. -- data AppUrl -- = Count Int -- | Echo Text -- | Paths [Text] -- deriving (Generic) -- -- \-\- Extend your App type to include a routing function. -- data App = App -- { _routeFn :: AppUrl -> [(Text, Maybe Text)] -> Text -- } -- -- \-\- Thanks to the wonders of Generic, an empty instance -- \-\- definition is all we need. Alternately, you can implement -- \-\- toPathSegments and fromPathSegments yourself or use -- \-\- web-routes-th. -- instance PathInfo AppUrl -- -- \-\- Set URL (Handler App App) to your URL data type defined above -- \-\- and askRouteFn must point to the routing function you added to -- \-\- your App. -- instance MonadRoute (Handler App App) where -- type URL (Handler App App) = AppUrl -- askRouteFn = gets _routeFn -- @ -- -- Moving on to Site.hs. -- -- @ -- \-\- Snap.Web.Routes provides routing functions -- import Snap.Web.Routes -- -- \-\- Add your new routes using routeWith -- routes :: [(ByteString, Handler App App ())] -- routes = [ ("", routeWith routeAppUrl) -- , ("", serveDirectory "static") -- ] -- -- \-\- Define handlers for each data constructor in your URL data type. -- routeAppUrl :: AppUrl -> Handler App App () -- routeAppUrl appUrl = -- case appUrl of -- (Count n) -> writeText $ ("Count = " `T.append` (T.pack $ show n)) -- (Echo text) -> echo text -- (Paths ps) -> writeText $ T.intercalate " " ps -- -- \-\- You'll note that these are normal Snap handlers, except they can take -- \-\- values from the data constructor as arguments. This is a lot nicer than -- \-\- having to use getParam. -- echo :: T.Text -> Handler App App () -- echo msg = heistLocal (bindString "message" msg) $ render "echo" -- -- \-\- Add the routing function to your app. -- app :: SnapletInit App App -- app = makeSnaplet "app" "An example snap-web-routes app." Nothing $ do -- addRoutes routes -- return $ App renderRoute -- @ -- -- If you prefixed the routes in routeWith -- -- > ("/prefix", routeWith routeAppUrl) -- -- then use `renderRouteWithPrefix` instead: -- -- > return . App $ renderRouteWithPrefix "/prefix" -- -- If you are having trouble figuring out why a particular request isn't routing -- as expected, try replacing 'routeWith' with 'routeWithDebug'. It'll display -- the available routes, as well as any failed route parses. Just remember that -- it's not suitable for production use. module Snap.Web.Routes ( routeWith , routeWithDebug , renderRoute , renderRouteWithPrefix , heistUrl ) where import Control.Monad.State (lift, gets) import Data.Text (Text, append, pack) import Heist (HeistT) import Snap.Core import Snap.Snaplet import Snap.Web.Routes.Heist import Web.Routes ------------------------------------------------------------------------------ -- | Given a routing function, routes matching requests or calls -- 'Snap.Core.pass'. routeWith :: (PathInfo url, MonadSnap m) => (url -> m ()) -- ^ routing function -> m () routeWith = flip routeWithOr $ const pass ------------------------------------------------------------------------------ -- | Given a routing function, routes matching requests or returns debugging -- information. This is __not suitable for production__, but can be useful in -- seeing what paths are available or determining why a path isn't routing as -- expected. routeWithDebug :: (PathInfo url, MonadSnap m) => (url -> m ()) -- ^ routing function -> m () routeWithDebug = flip routeWithOr (\err -> writeText err) ------------------------------------------------------------------------------ routeWithOr :: (PathInfo url, MonadSnap m) => (url -> m ()) -> (Text -> m ()) -> m () routeWithOr router onLeft = do rq <- getRequest case fromPathInfo $ rqPathInfo rq of (Left e) -> onLeft . pack $ e (Right url) -> router url ------------------------------------------------------------------------------ -- | Turn a route and params into a path. renderRoute :: PathInfo url => url -- ^ URL data constructor -> [(Text, Maybe Text)] -- ^ parameters -> Text -- ^ rendered route renderRoute = renderRouteWithPrefix "" ------------------------------------------------------------------------------ -- | Turn a route and params into a path with the given prefix. renderRouteWithPrefix :: PathInfo url => Text -- ^ route prefix -> url -- ^ URL data constructor -> [(Text, Maybe Text)] -- ^ parameters -> Text -- ^ rendered route renderRouteWithPrefix p u params = p `append` toPathInfoParams u params ------------------------------------------------------------------------------ -- | MonadRoute instance for 'HeistT'. instance (MonadRoute m) => MonadRoute (HeistT n m) where type URL (HeistT n m) = URL m askRouteFn = lift askRouteFn