packages feed

snap-web-routes 0.4.0.0 → 0.5.0.0

raw patch · 8 files changed

+241/−72 lines, 8 filesdep +bytestringPVP ok

version bump matches the API change (PVP)

Dependencies added: bytestring

API changes (from Hackage documentation)

- Snap.Snaplet.Router.Heist: urlParamsSplice :: (HasRouter m, PathInfo (URL m)) => URL m -> [(Text, Maybe Text)] -> m [Node]
- Snap.Snaplet.Router.Heist: urlSplice :: (HasRouter m, PathInfo (URL m)) => URL m -> m [Node]
+ Snap.Snaplet.Router.HeistSplices: urlParamsSplice :: (HasRouter m, PathInfo (URL m)) => URL m -> [(Text, Maybe Text)] -> m [Node]
+ Snap.Snaplet.Router.HeistSplices: urlSplice :: (HasRouter m, PathInfo (URL m)) => URL m -> m [Node]
+ Snap.Snaplet.Router.REST: Edit :: id -> Resource id
+ Snap.Snaplet.Router.REST: EditS :: SingletonResource
+ Snap.Snaplet.Router.REST: Index :: Resource id
+ Snap.Snaplet.Router.REST: New :: Resource id
+ Snap.Snaplet.Router.REST: NewS :: SingletonResource
+ Snap.Snaplet.Router.REST: Show :: id -> Resource id
+ Snap.Snaplet.Router.REST: ShowS :: SingletonResource
+ Snap.Snaplet.Router.REST: data PathInfo id => Resource id
+ Snap.Snaplet.Router.REST: data SingletonResource
+ Snap.Snaplet.Router.REST: instance (Eq id, PathInfo id) => Eq (Resource id)
+ Snap.Snaplet.Router.REST: instance (Read id, PathInfo id) => Read (Resource id)
+ Snap.Snaplet.Router.REST: instance (Show id, PathInfo id) => Show (Resource id)
+ Snap.Snaplet.Router.REST: instance Eq SingletonResource
+ Snap.Snaplet.Router.REST: instance PathInfo SingletonResource
+ Snap.Snaplet.Router.REST: instance PathInfo id => PathInfo (Resource id)
+ Snap.Snaplet.Router.REST: instance Read SingletonResource
+ Snap.Snaplet.Router.REST: instance Show SingletonResource

Files

README.md view
@@ -2,15 +2,21 @@  ## Type safe URLs for Snap -[`Snap web routes`](http://hackage.haskell.org/package/snap-web-routes) provides type safe URLs for Snap using [`web routes`](http://hackage.haskell.org/package/web-routes).+[`Snap web routes`](http://hackage.haskell.org/package/snap-web-routes)+provides type safe URLs for Snap using+[`web routes`](http://hackage.haskell.org/package/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.+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+things 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.+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.  ```haskell -- Enable a few extensions@@ -27,15 +33,17 @@  -- Snap.Snaplet.Router.Types exports everything you need to -- define your PathInfo and HasRouter instances.-import Snap.Web.Routes.Types+import Snap.Snaplet.Router.Types  -- Your URL data type.  Deriving a `Generic` allows you to -- get a free `PathInfo` instance. data AppUrl-    = Count Int+    = Login+    | Logout+    | Count Int     | Echo Text     | Paths [Text]-      deriving (Generic)+      deriving (Eq, Show, Read, Generic)  -- Extend your App type to include the router snaplet. data App = App@@ -49,18 +57,17 @@ instance PathInfo AppUrl  -- You need to define a HasRouter instance for your app.--- @type URL (Handler App App)@ must be set to the URL--- data type you defined above.--- @with router@ uses the lens for the @RouterState@ snaplet+-- You must set type URL (Handler App App) to the URL+-- data type you defined above. The router in+-- `with router` is the lens for the @RouterState@ snaplet -- you added to App. instance HasRouter (Handler App App) where     type URL (Handler App App) = AppUrl     getRouterState = with router get  -- You also need to define a HasRouter instance for the--- router snaplet.--- @type URL (Handler b RouterState)@ must be set to the URL--- data type you defined above.+-- router snaplet. Once again, set type URL (Handler b+-- RouterState) to the data type you defined above. instance HasRouter (Handler b RouterState) where     type URL (Handler b RouterState) = AppUrl     getRouterState = get@@ -68,7 +75,8 @@  ### `Site.hs` -Moving on to `Site.hs`, we'll setup handlers for each URL, as well initialise our app with the router snaplet..+Moving on to `Site.hs`, we'll setup handlers for each URL, as well as initialise+our app with the router snaplet..  ```haskell -- Snap.Snaplet.Router provides routing functions@@ -84,6 +92,8 @@ routeAppUrl :: AppUrl -> Handler App App () routeAppUrl appUrl =     case appUrl of+      (Login)     -> with auth handleLoginSubmit+      (Logout)    -> with auth handleLogout       (Count n)   -> writeText $ ("Count = " `T.append` (T.pack $ show n))       (Echo text) -> echo text       (Paths ps)  -> writeText $ T.intercalate " " ps@@ -103,27 +113,66 @@     return $ App h r ``` -The prefix you pass to the router snaplet must match the prefix you specified in routes, e.g. if it was `("/prefix", routeWith routeAppUrl)`) then:+The prefix you pass to the router snaplet must match the prefix you specified+in routes, e.g. if it was `("/prefix", routeWith routeAppUrl)`) then:  ```haskell r <- nestSnaplet "router" router $ initRouter "/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, and only displays debugging information for local requests.+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, and only displays debugging+information for local requests. -### Rendering URLs+### Using URLs -Helper functions are provided for rendering URLs:+Let's look at how you can use your newly defined URL data type in your app.+Firstly, you'll probably want to add links in Heist views. This is easily+accomplished with the `urlSplice` and `urlParamsSplice` functions.  ```haskell-echo :: T.Text -> Handler App App ()-echo msg = do-    if msg == "test" then redirectURL (Echo "test passed") else renderEcho+linksHandler :: Handler App App ()+linksHandler = heistLocal (I.bindSplices linksSplices) $ render "links"   where-    renderEcho = heistLocal (I.bindSplices echoSplices) $ render "echo"-    echoSplices = do-        "message"  ## I.textSplice msg-        "countUrl" ## urlSplice (Count 10)+    linksSplices = do+        "loginUrl" ## urlSplice Login+        "echoUrl"  ## urlSplice (Echo "ping")+        "countUrl" ## urlParamsSplice (Count 10) [("explanation", Just "true")] ``` -In the example above you'll find we use `urlSplice` to turn a URL into a splice, and `redirectURL` to redirect to a URL. There is also `urlPath` to render a URL as Text, as well as params versions of all these functions (`urlParamsSplice`, `redirectURLParams` and `urlPathParams`) that take a params list to append as a query string.+As you can see, splicing URLs into Heist views is easily accomplished. You+will likely also want to redirect to the handler for a certain URL. To do+this we've got `redirectURL` and `redirectURLParams`. Let's look at an+example.++```haskell+doSomethingHandler :: Handler App App ()+doSomethingHandler = doSomething >> redirectURL Logout+```++However, you will sometimes wish to redirect within a handler that runs in a+snaplet other than the main app. With the router snaplet though, this is+easily done:++```haskell+handleLogout :: Handler App (AuthManager App) ()+handleLogout = logout >> (withTop router $ redirectURL (Echo "logged out"))+```++Lastly, you can render a URL as Text with `urlPath` and `urlPathParams`.++```haskell+messageHandler :: Handler App App ()+messageHandler = do+    pathText <- urlPath (Echo "hello")+    heistLocal (I.bindSplices $ messageSplices path) $ render "message"+  where+    messageSplices path = do+        "message"  ## I.textSplice $ "The path is " `append` pathText+```++Remember, for each of `urlSplice`, `redirectURL` and `urlPath` there is a+params version that takes a params list as an extra argument, and renders+the URL with the given params as a query string.
changelog view
@@ -1,3 +1,6 @@+* Add Resource and SingletonResource data types for RESTful routes+* Ignore trailing slashes+ 2014-05-07 0.4.0.0  * Make router a snaplet
snap-web-routes.cabal view
@@ -1,13 +1,34 @@ name:                snap-web-routes-version:             0.4.0.0+version:             0.5.0.0 synopsis:            Type safe URLs for Snap description:-    Type safe URL generation and routing for Snap using web-routes.+    Type safe URL generation and routing for Snap using <http://hackage.haskell.org/package/web-routes web-routes>, and builds on <https://github.com/stepcut/snap-web-routes-demo work>+    done by Jeremy Shaw.     .-    To get started, there is a __<https://github.com/lukerandall/snap-web-routes/blob/master/README.md tutorial on GitHub>__.+    Get started with the comprehensive  __<https://github.com/lukerandall/snap-web-routes/blob/master/README.md tutorial>__.     .-    This builds on <https://github.com/stepcut/snap-web-routes-demo work>-    done by Jeremy Shaw. Thanks Jeremy!+    = Brief overview+    .+    It allows you to define a data type that represents the routes in your application:+    .+    > data AppUrl+    >     = Login                   -- routes to /login+    >     | Logout                  -- routes to /logout+    >     | User (Resource UserId)  -- provides RESTful routes at /user+    .+    'Resource' is documented in Snap.Snaplet.Router.REST, and makes defining RESTful routes easier. Also provided are functions to use the URL data type in your app:+    .+    > someHandler :: Handler App App ()+    > someHandler :: doSomething >> redirectURL $ User Index+    .+    and to generate URLs in views:+    .+    > linksHandler :: Handler App App ()+    > linksHandler = heistLocal (I.bindSplices linksSplices) $ render "links"+    >   where+    >     linksSplices = do+    >         "loginUrl" ## urlSplice Login+    .  homepage:            https://github.com/lukerandall/snap-web-routes bug-reports:         https://github.com/lukerandall/snap-web-routes/issues@@ -30,13 +51,15 @@    exposed-modules:     Snap.Snaplet.Router-    Snap.Snaplet.Router.Heist+    Snap.Snaplet.Router.HeistSplices     Snap.Snaplet.Router.Internal.Types+    Snap.Snaplet.Router.REST     Snap.Snaplet.Router.Types     Snap.Snaplet.Router.URL    build-depends:     base                      >= 4.4     && < 5,+    bytestring                >= 0.9.1   && < 0.11,     heist                     >= 0.13    && < 0.14,     mtl                       >= 2       && < 3,     snap-core                 >= 0.9     && < 0.11,
src/Snap/Snaplet/Router.hs view
@@ -18,18 +18,19 @@    ) where  +import           Data.ByteString (ByteString)+import qualified Data.ByteString.Char8 as B import           Data.Text import           Snap.Core (MonadSnap) import qualified Snap.Core as SC import           Snap.Snaplet-import           Web.Routes (PathInfo, toPathInfoParams, fromPathInfo)+import           Web.Routes (PathInfo, fromPathInfo) -------------------------------------------------------------------------------import           Snap.Snaplet.Router.Heist+import           Snap.Snaplet.Router.HeistSplices import           Snap.Snaplet.Router.URL import           Snap.Snaplet.Router.Internal.Types  - ------------------------------------------------------------------------------ -- | Snaplet initializer. initRouter :: Text  -- ^ Prefix to add to paths.@@ -61,8 +62,19 @@ ------------------------------------------------------------------------------ routeWithOr     :: (PathInfo url, MonadSnap m) => (url -> m ()) -> (Text -> m ()) -> m ()-routeWithOr router leftFn =-    do rq <- SC.getRequest-       case fromPathInfo $ SC.rqPathInfo rq of-         (Left e)    -> leftFn . pack $ e+routeWithOr router onLeft = do+    rqPath <- rqPathInfoNormalized+    case fromPathInfo rqPath of+         (Left e)    -> onLeft . pack $ e          (Right url) -> router url++rqPathInfoNormalized+    :: (MonadSnap m) => m ByteString+rqPathInfoNormalized = do+    rq <- SC.getRequest+    return . dropTrailingSlashes $ SC.rqPathInfo rq+  where+    dropTrailingSlashes s =+        if B.singleton '/' `B.isSuffixOf` s+        then dropTrailingSlashes $ B.init s+        else s
− src/Snap/Snaplet/Router/Heist.hs
@@ -1,32 +0,0 @@-{-# LANGUAGE FlexibleContexts  #-}--module Snap.Snaplet.Router.Heist-  ( urlSplice-  , urlParamsSplice-  ) where---import Data.Text-import Snap.Snaplet.Router.Internal.Types-import Snap.Snaplet.Router.URL (urlPathParams)-import Text.XmlHtml hiding (render)-import Web.Routes (PathInfo)------------------------------------------------------------------------------------ | Returns the given URL as a `Heist` splice-urlSplice-    :: (HasRouter m, PathInfo (URL m)) => URL m  -- ^ URL data type-    -> m [Node]-urlSplice u = urlParamsSplice u []------------------------------------------------------------------------------------ | Returns the given URL as a `Heist` splice with query string for params-urlParamsSplice-    :: (HasRouter m, PathInfo (URL m)) => URL m  -- ^ URL data type-    -> [(Text, Maybe Text)]                      -- ^ Params-    -> m [Node]-urlParamsSplice u p = do-    t <- urlPathParams u p-    return [TextNode t]
+ src/Snap/Snaplet/Router/HeistSplices.hs view
@@ -0,0 +1,32 @@+{-# LANGUAGE FlexibleContexts  #-}++module Snap.Snaplet.Router.HeistSplices+  ( urlSplice+  , urlParamsSplice+  ) where+++import Data.Text+import Snap.Snaplet.Router.Internal.Types+import Snap.Snaplet.Router.URL (urlPathParams)+import Text.XmlHtml hiding (render)+import Web.Routes (PathInfo)+++------------------------------------------------------------------------------+-- | Returns the given URL as a `Heist` splice+urlSplice+    :: (HasRouter m, PathInfo (URL m)) => URL m  -- ^ URL data type+    -> m [Node]+urlSplice u = urlParamsSplice u []+++------------------------------------------------------------------------------+-- | Returns the given URL as a `Heist` splice with query string for params+urlParamsSplice+    :: (HasRouter m, PathInfo (URL m)) => URL m  -- ^ URL data type+    -> [(Text, Maybe Text)]                      -- ^ Params+    -> m [Node]+urlParamsSplice u p = do+    t <- urlPathParams u p+    return [TextNode t]
+ src/Snap/Snaplet/Router/REST.hs view
@@ -0,0 +1,83 @@+-- | This module provides data types useful for declaring RESTful routes.+-- 'Resource' is provided for the common case of resource specified+-- by a unique identifier (for e.g. @\/products\/3@). 'SingletonResource'+-- is provided for resources that do not require an identifier. Typical+-- usage would be as follows:+--+-- @+-- data AppUrl+--     = User (Resource Int)+--     | Profile SingletonResource+--     | Login+--     | Logout+-- @+--+-- This now allows you to define handlers for different end points for+-- your resource.+-- @+-- routeAppUrl appUrl =+--     case appUrl of+--       Login          -> with auth handleLogin   -- \/login+--       Logout         -> with auth handleLogout  -- \/logout+--       User Index     -> handleUserIndex         -- \/user+--       User New       -> handleUserNew           -- \/user\/new+--       User (Show n)  -> handleUserShow n        -- \/user\/:Int+--       User (Edit n)  -> handleUserEdit n        -- \/user\/:Int\/edit+--       Profile ShowS  -> handleProfileShow       -- \/profile+--       Profile NewS   -> handleProfileNew        -- \/profile/new+--       Profile EditS  -> handleProfileEdit       -- \/profile/edit+-- @+--++{-# LANGUAGE OverloadedStrings #-}++module Snap.Snaplet.Router.REST+    ( Resource (..)+    , SingletonResource (..)+    ) where+++import Control.Applicative+import Web.Routes.PathInfo (PathInfo (..), segment, toPathSegments, fromPathSegments)+++-- | A data type to represent RESTful resources that have a unique identifier.+-- The data type used as the identifier must be an instance of 'PathInfo'.+data (PathInfo id) => Resource id+    = Index+    | New+    | Show id+    | Edit id+      deriving (Eq, Show, Read)+++-- | A data type to represent singleton RESTful resources. Generally these+-- are identified in some other way, often the user's session.+data SingletonResource+    = ShowS+    | NewS+    | EditS+      deriving (Eq, Show, Read)+++instance (PathInfo id) => PathInfo (Resource id) where+    toPathSegments Index = []+    toPathSegments New   = ["new"]+    toPathSegments (Show n) = toPathSegments n+    toPathSegments (Edit n) = toPathSegments n ++ ["edit"]++    fromPathSegments =+            New <$ segment "new"+        <|> fromPathSegments <**> ((pure Edit <* segment "edit") <|> pure Show)+        <|> pure Index+++instance PathInfo SingletonResource where+    toPathSegments ShowS = []+    toPathSegments NewS  = ["new"]+    toPathSegments EditS = ["edit"]++    fromPathSegments =+            NewS <$ segment "new"+        <|> EditS <$ segment "edit"+        <|> pure ShowS
src/Snap/Snaplet/Router/URL.hs view
@@ -12,9 +12,8 @@ import           Data.Text.Encoding (encodeUtf8) import           Snap.Core (MonadSnap) import qualified Snap.Core as SC-import           Snap.Snaplet import           Snap.Snaplet.Router.Internal.Types-import           Web.Routes (PathInfo, toPathInfoParams, fromPathInfo)+import           Web.Routes (PathInfo, toPathInfoParams)   ------------------------------------------------------------------------------