@hackage snap-web-routes0.3.0.1

Type safe URLs for Snap

snap-web-routes

Type safe URLs for Snap

Snap web routes provides type safe URLs for Snap using web routes.

How to use

The tutorial assumes you have a standard Snap app layout with an Application.hs and Site.hs. If your setup differs you'll need to adapt the instructions accordingly.

Application.hs

To get going, you'll need to add a few things to Application.hs. This includes creating the URL data type and adding the routing function to our App data type.

-- Enable a few extensions
{-# LANGUAGE FlexibleInstances #-} -- Needed by
{-# LANGUAGE TypeFamilies      #-} -- web-routes
{-# LANGUAGE DeriveGeneric     #-} -- Needed to derive Generic
                                   -- for our URL data type

-- Paths and params use 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

Site.hs

Moving on to Site.hs, we'll setup handlers for each URL, as well initialise our app with a routing function.

-- 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 value 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 value 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 (e.g. ("/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.

Rendering URLs

Helper functions are provided for rendering URLs:

echo :: T.Text -> Handler App App ()
echo msg = do
    url <- showUrl $ Echo "this is a test"
    if msg == "test" then redirect (encodeUtf8 url) else renderEcho
  where
    renderEcho = heistLocal (I.bindSplices echoSplices) $ render "echo"
    echoSplices = do
        "message"  ## I.textSplice msg
        "countUrl" ## urlSplice (Count 10)

In the example above you'll find we use urlSplice to turn a URL into a splice, and showUrl to render a URL as Text.