packages feed

Spock 0.7.9.0 → 0.7.10.0

raw patch · 15 files changed

+301/−109 lines, 15 filesdep +list-tdep +stm-containersdep ~aesondep ~basedep ~monad-controlPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

Dependencies added: list-t, stm-containers

Dependency ranges changed: aeson, base, monad-control, reroute, stm

API changes (from Hackage documentation)

- Web.Spock.Shared: data WebStateM conn sess st a
+ Web.Spock.Internal.SessionVault: SessionVault :: Map (SessionKey s) s -> SessionVault s
+ Web.Spock.Internal.SessionVault: class (Eq (SessionKey s), Hashable (SessionKey s)) => IsSession s where type family SessionKey s :: *
+ Web.Spock.Internal.SessionVault: deleteSession :: IsSession s => SessionKey s -> SessionVault s -> STM ()
+ Web.Spock.Internal.SessionVault: filterSessions :: IsSession s => (s -> Bool) -> SessionVault s -> STM ()
+ Web.Spock.Internal.SessionVault: getSessionKey :: IsSession s => s -> SessionKey s
+ Web.Spock.Internal.SessionVault: instance IsSession (Session conn sess st)
+ Web.Spock.Internal.SessionVault: loadSession :: IsSession s => SessionKey s -> SessionVault s -> STM (Maybe s)
+ Web.Spock.Internal.SessionVault: newSessionVault :: IsSession s => STM (SessionVault s)
+ Web.Spock.Internal.SessionVault: newtype SessionVault s
+ Web.Spock.Internal.SessionVault: storeSession :: IsSession s => s -> SessionVault s -> STM ()
+ Web.Spock.Internal.SessionVault: toList :: IsSession s => SessionVault s -> STM [s]
+ Web.Spock.Internal.SessionVault: unSessionVault :: SessionVault s -> Map (SessionKey s) s
+ Web.Spock.Safe: getpost :: MonadIO m => Path xs -> HVectElim xs (ActionT m ()) -> SpockT m ()
+ Web.Spock.Shared: defaultSessionCfg :: a -> SessionCfg a
+ Web.Spock.Shared: modifyReadSession :: (sess -> sess) -> SpockAction conn sess st sess
+ Web.Spock.Shared: modifySession' :: (sess -> (sess, a)) -> SpockAction conn sess st a
+ Web.Spock.Shared: sc_housekeepingInterval :: SessionCfg a -> NominalDiffTime
+ Web.Spock.Shared: type WebStateM conn sess st = WebStateT conn sess st (ResourceT IO)
+ Web.Spock.Simple: getpost :: MonadIO m => SpockRoute -> ActionT m () -> SpockT m ()
- Web.Spock.Safe: renderRoute :: HasRep as => Path as -> HVectElim as Text
+ Web.Spock.Safe: renderRoute :: Path as -> HVectElim as Text
- Web.Spock.Shared: SessionCfg :: Text -> NominalDiffTime -> Int -> Bool -> a -> Maybe (SessionPersistCfg a) -> SessionCfg a
+ Web.Spock.Shared: SessionCfg :: Text -> NominalDiffTime -> Int -> Bool -> a -> Maybe (SessionPersistCfg a) -> NominalDiffTime -> SessionCfg a

Files

Spock.cabal view
@@ -1,7 +1,17 @@ name:                Spock-version:             0.7.9.0+version:             0.7.10.0 synopsis:            Another Haskell web framework for rapid development-description:         This toolbox provides everything you need to get a quick start into web hacking with haskell: fast routing, middleware, json, sessions, cookies, database helper, csrf-protection+description:         This toolbox provides everything you need to get a quick start into web hacking with haskell:+                     .+                     * fast routing+                     * middleware+                     * json+                     * sessions+                     * cookies+                     * database helper+                     * csrf-protection+                     .+                     A tutorial is available at <http://www.spock.li/tutorial/ spock.li> Homepage:            http://www.spock.li Bug-reports:         https://github.com/agrafix/Spock/issues license:             BSD3@@ -17,7 +27,9 @@ library   hs-source-dirs:      src   exposed-modules:+                       Web.Spock,                        Web.Spock.Internal.Util,+                       Web.Spock.Internal.SessionVault,                        Web.Spock.Safe,                        Web.Spock.Shared,                        Web.Spock.Simple@@ -29,7 +41,7 @@                        Web.Spock.Internal.Types,                        Web.Spock.Internal.Wire   build-depends:-                       aeson >= 0.6.2.1,+                       aeson >= 0.7,                        base >= 4 && < 5,                        base64-bytestring >=1.0,                        bytestring >=0.10,@@ -38,15 +50,17 @@                        directory >=1.2,                        hashable >=1.2,                        http-types >=0.8,-                       monad-control >=0.3,+                       list-t >=0.4,+                       monad-control >=1.0,                        mtl >=2.1,                        old-locale >=1.0,                        path-pieces >=0.1.4,                        random >=1.0,-                       reroute >=0.2.2.1,+                       reroute >=0.2.3,                        resource-pool >=0.2,                        resourcet >= 0.4,                        stm >=2.4,+                       stm-containers >=0.2,                        text >= 0.11.3.1,                        time >=1.4,                        transformers >=0.3,@@ -65,6 +79,7 @@   other-modules:                        Web.Spock.FrameworkSpecHelper,                        Web.Spock.Internal.UtilSpec,+                       Web.Spock.Internal.SessionVaultSpec,                        Web.Spock.SafeSpec,                        Web.Spock.SimpleSpec   build-depends:@@ -73,6 +88,7 @@                        hspec-wai >= 0.6,                        http-types,                        Spock,+                       stm,                        reroute,                        text,                        wai
+ src/Web/Spock.hs view
@@ -0,0 +1,10 @@+{- |+This module reexports type safe routing aproach which should be the default for all Spock applications. To learn more about+the routing, read the corresponding blog post available at <http://www.spock.li/2015/04/19/type-safe_routing.html>+-}+module Web.Spock+    ( module Web.Spock.Safe+    )+where++import Web.Spock.Safe
src/Web/Spock/Internal/Core.hs view
@@ -61,7 +61,7 @@                , web_sessionMgr = sessionMgr                , web_state = initialState                }-       spockAllT regIf (\m -> runResourceT $ runReaderT (runWebStateM m) internalState) $+       spockAllT regIf (\m -> runResourceT $ runReaderT (runWebStateT m) internalState) $                do defs                   middleware (sm_middleware sessionMgr) 
src/Web/Spock/Internal/Monad.hs view
@@ -43,7 +43,7 @@  -- | Run an action inside of Spocks core monad. This allows you to use runQuery and getState runSpockIO :: WebState conn sess st -> WebStateM conn sess st a -> IO a-runSpockIO st (WebStateM action) =+runSpockIO st (WebStateT action) =     runResourceT $     runReaderT action st 
src/Web/Spock/Internal/SessionManager.hs view
@@ -1,7 +1,7 @@ {-# LANGUAGE CPP #-} {-# LANGUAGE FlexibleContexts, OverloadedStrings, DoAndIfThenElse, RankNTypes #-} module Web.Spock.Internal.SessionManager-    ( createSessionManager+    ( createSessionManager, withSessionManager     , SessionId, Session(..), SessionManager(..)     ) where@@ -9,13 +9,14 @@ import Web.Spock.Internal.Types import Web.Spock.Internal.CoreAction import Web.Spock.Internal.Util+import qualified Web.Spock.Internal.SessionVault as SV  import Control.Arrow (first) import Control.Concurrent import Control.Concurrent.STM+import Control.Exception import Control.Monad import Control.Monad.Trans-import Data.List (foldl') import Data.Time #if MIN_VERSION_time(1,5,0) #else@@ -33,12 +34,20 @@ import qualified Data.Vault.Lazy as V import qualified Network.Wai as Wai +withSessionManager :: SessionCfg sess -> (SessionManager conn sess st -> IO a) -> IO a+withSessionManager sessCfg =+    bracket (createSessionManager sessCfg) sm_closeSessionManager+ createSessionManager :: SessionCfg sess -> IO (SessionManager conn sess st) createSessionManager cfg =     do oldSess <- loadSessions-       cacheHM <- atomically $ newTVar oldSess+       cacheHM <-+           atomically $+           do mapV <- SV.newSessionVault+              forM_ oldSess $ \v -> SV.storeSession v mapV+              return mapV        vaultKey <- V.newKey-       _ <- forkIO (forever (housekeepSessions cacheHM storeSessions))+       housekeepThread <- forkIO (forever (housekeepSessions cfg cacheHM storeSessions))        return           SessionManager           { sm_getSessionId = getSessionIdImpl vaultKey cacheHM@@ -50,52 +59,59 @@           , sm_addSafeAction = addSafeActionImpl vaultKey cacheHM           , sm_lookupSafeAction = lookupSafeActionImpl vaultKey cacheHM           , sm_removeSafeAction = removeSafeActionImpl vaultKey cacheHM+          , sm_closeSessionManager = killThread housekeepThread           }     where       (loadSessions, storeSessions) =           case sc_persistCfg cfg of             Nothing ->-                ( return HM.empty+                ( return []                 , const $ return ()                 )             Just spc ->                 ( do sessions <- spc_load spc-                     return $ foldl' genSession HM.empty sessions+                     return (map genSession sessions)                 , spc_store spc . map mkSerializable . HM.elems                 )       mkSerializable sess =           (sess_id sess, sess_validUntil sess, sess_data sess)-      genSession hm (sid, validUntil, theData) =-          let s =-                  Session-                  { sess_id = sid-                  , sess_validUntil = validUntil-                  , sess_data = theData-                  , sess_safeActions = SafeActionStore HM.empty HM.empty-                  }-          in HM.insert sid s hm+      genSession (sid, validUntil, theData) =+          Session+          { sess_id = sid+          , sess_validUntil = validUntil+          , sess_data = theData+          , sess_safeActions = SafeActionStore HM.empty HM.empty+          }  getSessionIdImpl :: V.Key SessionId-                 -> UserSessions conn sess st+                 -> SV.SessionVault (Session conn sess st)                  -> SpockAction conn sess st SessionId getSessionIdImpl vK sessionRef =     do sess <- readSessionBase vK sessionRef        return $ sess_id sess  modifySessionBase :: V.Key SessionId-                  -> UserSessions conn sess st-                  -> (Session conn sess st -> Session conn sess st)-                  -> SpockAction conn sess st ()+                  -> SV.SessionVault (Session conn sess st)+                  -> (Session conn sess st -> (Session conn sess st, a))+                  -> SpockAction conn sess st a modifySessionBase vK sessionRef modFun =     do req <- request        case V.lookup vK (Wai.vault req) of          Nothing ->              error "(3) Internal Spock Session Error. Please report this bug!"          Just sid ->-             liftIO $ atomically $ modifyTVar' sessionRef (HM.adjust modFun sid)+             liftIO $ atomically $+             do mSession <- SV.loadSession sid sessionRef+                case mSession of+                  Nothing ->+                      fail "Internal Spock Session Error: Unknown SessionId"+                  Just session ->+                      do let (sessionNew, result) = modFun session+                         SV.storeSession sessionNew sessionRef+                         return result  readSessionBase :: V.Key SessionId-                -> UserSessions conn sess st+                -> SV.SessionVault (Session conn sess st)                 -> SpockAction conn sess st (Session conn sess st) readSessionBase vK sessionRef =     do req <- request@@ -103,15 +119,15 @@          Nothing ->              error "(1) Internal Spock Session Error. Please report this bug!"          Just sid ->-             do sessions <- liftIO $ atomically $ readTVar sessionRef-                case HM.lookup sid sessions of+             do mSession <- liftIO $ atomically $ SV.loadSession sid sessionRef+                case mSession of                   Nothing ->                       error "(2) Internal Spock Session Error. Please report this bug!"                   Just session ->                       return session  addSafeActionImpl :: V.Key SessionId-                  -> UserSessions conn sess st+                  -> SV.SessionVault (Session conn sess st)                   -> PackedSafeAction conn sess st                   -> SpockAction conn sess st SafeActionHash addSafeActionImpl vaultKey sessionMapVar safeAction =@@ -126,11 +142,11 @@                         { sas_forward = HM.insert safeActionHash safeAction (sas_forward sas)                         , sas_reverse = HM.insert safeAction safeActionHash (sas_reverse sas)                         }-                modifySessionBase vaultKey sessionMapVar (\s -> s { sess_safeActions = f (sess_safeActions s) })+                modifySessionBase vaultKey sessionMapVar (\s -> (s { sess_safeActions = f (sess_safeActions s) }, ()))                 return safeActionHash  lookupSafeActionImpl :: V.Key SessionId-                     -> UserSessions conn sess st+                     -> SV.SessionVault (Session conn sess st)                      -> SafeActionHash                      -> SpockAction conn sess st (Maybe (PackedSafeAction conn sess st)) lookupSafeActionImpl vaultKey sessionMapVar hash =@@ -138,11 +154,11 @@        return $ HM.lookup hash (sas_forward (sess_safeActions base))  removeSafeActionImpl :: V.Key SessionId-                     -> UserSessions conn sess st+                     -> SV.SessionVault (Session conn sess st)                      -> PackedSafeAction conn sess st                      -> SpockAction conn sess st () removeSafeActionImpl vaultKey sessionMapVar action =-    modifySessionBase vaultKey sessionMapVar (\s -> s { sess_safeActions = f (sess_safeActions s ) })+    modifySessionBase vaultKey sessionMapVar (\s -> (s { sess_safeActions = f (sess_safeActions s ) }, ()))     where       f sas =           sas@@ -154,31 +170,32 @@           }  readSessionImpl :: V.Key SessionId-                -> UserSessions conn sess st+                -> SV.SessionVault (Session conn sess st)                 -> SpockAction conn sess st sess readSessionImpl vK sessionRef =     do base <- readSessionBase vK sessionRef        return (sess_data base)  writeSessionImpl :: V.Key SessionId-                 -> UserSessions conn sess st+                 -> SV.SessionVault (Session conn sess st)                  -> sess                  -> SpockAction conn sess st () writeSessionImpl vK sessionRef value =-    modifySessionImpl vK sessionRef (const value)+    modifySessionImpl vK sessionRef (const (value, ()))  modifySessionImpl :: V.Key SessionId-                  -> UserSessions conn sess st-                  -> (sess -> sess)-                  -> SpockAction conn sess st ()+                  -> SV.SessionVault (Session conn sess st)+                  -> (sess -> (sess, a))+                  -> SpockAction conn sess st a modifySessionImpl vK sessionRef f =     do let modFun session =-                        session { sess_data = f (sess_data session) }+               let (sessData', out) = f (sess_data session)+               in (session { sess_data = sessData' }, out)        modifySessionBase vK sessionRef modFun  sessionMiddleware :: SessionCfg sess                   -> V.Key SessionId-                  -> UserSessions conn sess st+                  -> SV.SessionVault (Session conn sess st)                   -> Wai.Middleware sessionMiddleware cfg vK sessionRef app req respond =     case getCookieFromReq (sc_cookieName cfg) of@@ -227,22 +244,22 @@              withSess True newSess  newSessionImpl :: SessionCfg sess-               -> UserSessions conn sess st+               -> SV.SessionVault (Session conn sess st)                -> sess                -> IO (Session conn sess st) newSessionImpl sessCfg sessionRef content =     do sess <- createSession sessCfg content-       atomically $ modifyTVar' sessionRef (HM.insert (sess_id sess) sess)+       atomically $ SV.storeSession sess sessionRef        return $! sess  loadSessionImpl :: SessionCfg sess-                -> UserSessions conn sess st+                -> SV.SessionVault (Session conn sess st)                 -> SessionId                 -> IO (Maybe (Session conn sess st)) loadSessionImpl sessCfg sessionRef sid =-    do sessHM <- atomically $ readTVar sessionRef+    do mSess <- atomically $ SV.loadSession sid sessionRef        now <- getCurrentTime-       case HM.lookup sid sessHM of+       case mSess of          Just sess ->              do sessWithPossibleExpansion <-                     if sc_sessionExpandTTL sessCfg@@ -251,7 +268,7 @@                                     { sess_validUntil =                                           addUTCTime (sc_sessionTTL sessCfg) now                                     }-                            atomically $ modifyTVar' sessionRef (HM.insert sid expandedSession)+                            atomically $ SV.storeSession expandedSession sessionRef                             return expandedSession                     else return sess                 if sess_validUntil sessWithPossibleExpansion > now@@ -261,33 +278,29 @@          Nothing ->              return Nothing -deleteSessionImpl :: UserSessions conn sess st+deleteSessionImpl :: SV.SessionVault (Session conn sess st)                   -> SessionId                   -> IO () deleteSessionImpl sessionRef sid =-    do atomically $ modifyTVar' sessionRef (HM.delete sid)-       return ()+    atomically $ SV.deleteSession sid sessionRef -clearAllSessionsImpl :: UserSessions conn sess st+clearAllSessionsImpl :: SV.SessionVault (Session conn sess st)                      -> SpockAction conn sess st () clearAllSessionsImpl sessionRef =-    liftIO $ atomically $ modifyTVar' sessionRef (const HM.empty)+    liftIO $ atomically $ SV.filterSessions (const False) sessionRef -housekeepSessions :: UserSessions conn sess st+housekeepSessions :: SessionCfg sess+                  -> SV.SessionVault (Session conn sess st)                   -> (HM.HashMap SessionId (Session conn sess st) -> IO ())                   -> IO ()-housekeepSessions sessionRef storeSessions =+housekeepSessions cfg sessionRef storeSessions =     do now <- getCurrentTime        newStatus <-            atomically $-           do modifyTVar' sessionRef (killOld now)-              readTVar sessionRef-       storeSessions newStatus-       threadDelay (1000 * 1000 * 60) -- 60 seconds-    where-      filterOld now (_, sess) = sess_validUntil sess > now-      killOld now hm =-          HM.fromList $ filter (filterOld now) $ HM.toList hm+           do SV.filterSessions (\sess -> sess_validUntil sess > now) sessionRef+              SV.toList sessionRef+       storeSessions (HM.fromList $ map (\v -> (SV.getSessionKey v, v)) newStatus)+       threadDelay (1000 * 1000 * (round $ sc_housekeepingInterval cfg))  createSession :: SessionCfg sess -> sess -> IO (Session conn sess st) createSession sessCfg content =
+ src/Web/Spock/Internal/SessionVault.hs view
@@ -0,0 +1,57 @@+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE CPP #-}+module Web.Spock.Internal.SessionVault where++import Web.Spock.Internal.Types++#if MIN_VERSION_base(4,8,0)+#else+import Control.Applicative+#endif+import Control.Concurrent.STM (STM)+import Control.Monad+import Data.Hashable+import qualified ListT as L+import qualified STMContainers.Map as STMMap+import qualified Data.Text as T++class (Eq (SessionKey s), Hashable (SessionKey s)) => IsSession s where+    type SessionKey s :: *+    getSessionKey :: s -> SessionKey s++instance IsSession (Session conn sess st) where+    type SessionKey (Session conn sess st) = T.Text+    getSessionKey = sess_id++newtype SessionVault s+    = SessionVault { unSessionVault :: STMMap.Map (SessionKey s) s }++-- | Create a new session vault+newSessionVault :: IsSession s => STM (SessionVault s)+newSessionVault = SessionVault <$> STMMap.new++-- | Load a session+loadSession :: IsSession s => SessionKey s -> SessionVault s -> STM (Maybe s)+loadSession key (SessionVault smap) = STMMap.lookup key smap++-- | Store a session, overwriting any previous values+storeSession :: IsSession s => s -> SessionVault s -> STM ()+storeSession v (SessionVault smap) = STMMap.insert v (getSessionKey v) smap++-- | Removea session+deleteSession :: IsSession s => SessionKey s -> SessionVault s -> STM ()+deleteSession k (SessionVault smap) = STMMap.delete k smap++-- | Get all sessions as list+toList :: IsSession s => SessionVault s -> STM [s]+toList =  liftM (map snd) . L.toList . STMMap.stream . unSessionVault++-- | Remove all sessions that do not match the predicate+filterSessions :: IsSession s => (s -> Bool) -> SessionVault s -> STM ()+filterSessions cond sv =+    do allVals <- toList sv+       let deleteKeys =+               map getSessionKey $+               filter (not . cond) allVals+       forM_ deleteKeys $ flip deleteSession sv
src/Web/Spock/Internal/Types.hs view
@@ -1,10 +1,12 @@-{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE CPP #-}+{-# LANGUAGE ExistentialQuantification #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE MultiParamTypeClasses #-}-{-# LANGUAGE ConstraintKinds #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RankNTypes #-} {-# LANGUAGE TypeFamilies #-}-{-# LANGUAGE ExistentialQuantification #-}-{-# LANGUAGE CPP #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE UndecidableInstances #-} module Web.Spock.Internal.Types where  import Web.Spock.Internal.CoreAction@@ -14,7 +16,6 @@ #else import Control.Applicative #endif-import Control.Concurrent.STM import Control.Monad.Base import Control.Monad.Reader import Control.Monad.Trans.Control@@ -58,6 +59,19 @@    = PCPool (Pool a)    | PCConn (ConnBuilder a) +-- | Session configuration with reasonable defaults+defaultSessionCfg :: a -> SessionCfg a+defaultSessionCfg emptySession =+    SessionCfg+    { sc_cookieName = "spockcookie"+    , sc_sessionTTL = 3600+    , sc_sessionIdEntropy = 64+    , sc_sessionExpandTTL = True+    , sc_emptySession = emptySession+    , sc_persistCfg = Nothing+    , sc_housekeepingInterval = 60 * 10+    }+ -- | Configuration for the session manager data SessionCfg a    = SessionCfg@@ -73,6 +87,8 @@      -- ^ initial session for visitors    , sc_persistCfg :: Maybe (SessionPersistCfg a)      -- ^ persistence interface for sessions+   , sc_housekeepingInterval :: NominalDiffTime+     -- ^ how often should the session manager check for dangeling dead sessions    }  data SessionPersistCfg a@@ -124,25 +140,24 @@  type SafeActionHash = T.Text -newtype WebStateM conn sess st a = WebStateM { runWebStateM :: ReaderT (WebState conn sess st) (ResourceT IO) a }-    deriving (Monad, Functor, Applicative, MonadIO, MonadReader (WebState conn sess st))+newtype WebStateT conn sess st m a = WebStateT { runWebStateT :: ReaderT (WebState conn sess st) m a }+    deriving (Monad, Functor, Applicative, MonadIO, MonadReader (WebState conn sess st), MonadTrans) -instance MonadBase IO (WebStateM conn sess st) where-    liftBase = WebStateM . liftBase+instance MonadBase b m => MonadBase b (WebStateT conn sess st m) where+    liftBase = liftBaseDefault -#if MIN_VERSION_monad_control(1,0,0)-newtype WStM conn sess st a = WStM { unWStM :: StM (ReaderT (WebState conn sess st) (ResourceT IO)) a }-#endif+instance MonadTransControl (WebStateT conn sess st) where+    type StT (WebStateT conn sess st) a = a+    liftWith = defaultLiftWith WebStateT runWebStateT+    restoreT = defaultRestoreT WebStateT -instance MonadBaseControl IO (WebStateM conn sess st) where-#if MIN_VERSION_monad_control(1,0,0)-    type StM (WebStateM conn sess st) a = WStM conn sess st a-#else-    newtype StM (WebStateM conn sess st) a = WStM { unWStM :: StM (ReaderT (WebState conn sess st) (ResourceT IO)) a }-#endif-    liftBaseWith f = WebStateM . liftBaseWith $ \runInBase -> f $ liftM WStM . runInBase . runWebStateM-    restoreM = WebStateM . restoreM . unWStM+instance MonadBaseControl b m => MonadBaseControl b (WebStateT conn sess st m) where+    type StM (WebStateT conn sess st m) a = ComposeSt (WebStateT conn sess st) m a+    restoreM = defaultRestoreM+    liftBaseWith = defaultLiftBaseWith +type WebStateM conn sess st = WebStateT conn sess st (ResourceT IO)+ type SessionId = T.Text data Session conn sess st     = Session@@ -151,21 +166,20 @@     , sess_data :: !sess     , sess_safeActions :: !(SafeActionStore conn sess st)     }+ instance Show (Session conn sess st) where     show = show . sess_id -type UserSessions conn sess st =-    TVar (HM.HashMap SessionId (Session conn sess st))- data SessionManager conn sess st    = SessionManager    { sm_getSessionId :: SpockAction conn sess st SessionId    , sm_readSession :: SpockAction conn sess st sess    , sm_writeSession :: sess -> SpockAction conn sess st ()-   , sm_modifySession :: (sess -> sess) -> SpockAction conn sess st ()+   , sm_modifySession :: forall a. (sess -> (sess, a)) -> SpockAction conn sess st a    , sm_clearAllSessions :: SpockAction conn sess st ()    , sm_middleware :: Middleware    , sm_addSafeAction :: PackedSafeAction conn sess st -> SpockAction conn sess st SafeActionHash    , sm_lookupSafeAction :: SafeActionHash -> SpockAction conn sess st (Maybe (PackedSafeAction conn sess st))    , sm_removeSafeAction :: PackedSafeAction conn sess st -> SpockAction conn sess st ()+   , sm_closeSessionManager :: IO ()    }
src/Web/Spock/Internal/Wire.hs view
@@ -84,6 +84,10 @@     | ActionMiddlewarePass     deriving (Show) +instance Monoid ActionInterupt where+    mempty = ActionDone+    mappend _ a = a+ #if MIN_VERSION_mtl(2,2,0) type ErrorT = ExceptT runErrorT :: ExceptT e m a -> m (Either e a)@@ -96,7 +100,7 @@  newtype ActionT m a     = ActionT { runActionT :: ErrorT ActionInterupt (RWST RequestInfo () ResponseState m) a }-      deriving (Monad, Functor, Applicative, MonadIO, MonadReader RequestInfo, MonadState ResponseState, MonadError ActionInterupt)+      deriving (Monad, Functor, Applicative, Alternative, MonadIO, MonadReader RequestInfo, MonadState ResponseState, MonadError ActionInterupt)  instance MonadTrans ActionT where     lift = ActionT . lift . lift
src/Web/Spock/Safe.hs view
@@ -6,6 +6,11 @@ {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE ScopedTypeVariables #-}++{- |+This module implements the type safe routing aproach. It should be used by all new Spock powered applications. To learn more about+the routing, read the corresponding blog post available at <http://www.spock.li/2015/04/19/type-safe_routing.html>+-} module Web.Spock.Safe     ( -- * Spock's route definition monad       spock, SpockM@@ -16,7 +21,7 @@     , renderRoute      -- * Hooking routes     , subcomponent-    , get, post, head, put, delete, patch, hookRoute, hookAny+    , get, post, getpost, head, put, delete, patch, hookRoute, hookAny     , Http.StdMethod (..)       -- * Adding Wai.Middleware     , middleware@@ -82,6 +87,10 @@ post :: MonadIO m => Path xs -> HVectElim xs (ActionT m ()) -> SpockT m () post = hookRoute POST +-- | Specify an action that will be run when the HTTP verb 'GET'/'POST' and the given route match+getpost :: MonadIO m => Path xs -> HVectElim xs (ActionT m ()) -> SpockT m ()+getpost r a = hookRoute POST r a >> hookRoute GET r a+ -- | Specify an action that will be run when the HTTP verb 'HEAD' and the given route match head :: MonadIO m => Path xs -> HVectElim xs (ActionT m ()) -> SpockT m () head = hookRoute HEAD@@ -160,8 +169,7 @@                    , SpockState (SpockAction conn sess st) ~ st)                 => SpockM conn sess st () hookSafeActions =-    do get (static "h" </> var) run-       post (static "h" </> var) run+    getpost (static "h" </> var) run     where       run h =           do mgr <- getSessMgr@@ -179,5 +187,5 @@ (<//>) = (</>)  -- | Render a route applying path pieces-renderRoute :: HasRep as => Path as -> HVectElim as T.Text-renderRoute route = hVectCurry (T.cons '/' . SR.renderRoute route)+renderRoute :: Path as -> HVectElim as T.Text+renderRoute route = hVectCurryExpl (pathToRep route) (T.cons '/' . SR.renderRoute route)
src/Web/Spock/Shared.hs view
@@ -28,9 +28,11 @@       -- * Basic HTTP-Auth     , requireBasicAuth      -- * Sessions-    , SessionCfg (..), SessionPersistCfg(..), SessionId-    , readShowSessionPersist-    , getSessionId, readSession, writeSession, modifySession, clearAllSessions+    , defaultSessionCfg, SessionCfg (..)+    , SessionPersistCfg(..), readShowSessionPersist+    , SessionId+    , getSessionId, readSession, writeSession+    , modifySession, modifySession', modifyReadSession, clearAllSessions      -- * Internals for extending Spock     , getSpockHeart, runSpockIO, WebStateM, WebState     )@@ -74,8 +76,20 @@ -- | Modify the stored session modifySession :: (sess -> sess) -> SpockAction conn sess st () modifySession f =+    modifySession' $ \sess -> (f sess, ())++-- | Modify the stored session and return a value+modifySession' :: (sess -> (sess, a)) -> SpockAction conn sess st a+modifySession' f =     do mgr <- getSessMgr        sm_modifySession mgr f++-- | Modify the stored session and return the new value after modification+modifyReadSession :: (sess -> sess) -> SpockAction conn sess st sess+modifyReadSession f =+    modifySession' $ \sess ->+        let x = f sess+        in (x, x)  -- | Read the stored session readSession :: SpockAction conn sess st sess
src/Web/Spock/Simple.hs view
@@ -5,6 +5,10 @@ {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE ScopedTypeVariables #-}+{- |+Since version 0.7 Spock features a new routing system that enables more type-safe code while still being relatively simple and lightweight.+You should consider using that (see "Web.Spock.Safe") instead of this module. This module is not yet deprecated, but this may happen anytime soon.+-} module Web.Spock.Simple     ( -- * Spock's route definition monad       spock, SpockM@@ -13,7 +17,7 @@     , SpockRoute, (<//>)      -- * Hooking routes     , subcomponent-    , get, post, head, put, delete, patch, hookRoute, hookAny+    , get, post, getpost, head, put, delete, patch, hookRoute, hookAny     , Http.StdMethod (..)      -- * Adding Wai.Middleware     , middleware@@ -97,6 +101,10 @@ post :: MonadIO m => SpockRoute -> ActionT m () -> SpockT m () post = hookRoute POST +-- | Specify an action that will be run when the HTTP verb 'GET'/'POST' and the given route match+getpost :: MonadIO m => SpockRoute -> ActionT m () -> SpockT m ()+getpost r a = hookRoute POST r a >> hookRoute GET r a+ -- | Specify an action that will be run when the HTTP verb 'HEAD' and the given route match head :: MonadIO m => SpockRoute -> ActionT m () -> SpockT m () head = hookRoute HEAD@@ -176,8 +184,7 @@                    , SpockState (SpockAction conn sess st) ~ st)                 => SpockM conn sess st () hookSafeActions =-    do get ("h" <//> ":spock-csurf-protection") run-       post ("h" <//> ":spock-csurf-protection") run+    getpost ("h" <//> ":spock-csurf-protection") run     where       run =           do Just h <- param "spock-csurf-protection"
test/Web/Spock/FrameworkSpecHelper.hs view
@@ -19,10 +19,12 @@             get "/" `shouldRespondWith` "root" { matchStatus = 200 }          it "routes different HTTP-verbs to different actions" $             do verbTest get "GET"-               verbTest (\p -> post p "") "POST"-               verbTest (\p -> put p "") "PUT"+               verbTest (`post` "") "POST"+               verbTest (`put` "") "PUT"                verbTest delete "DELETE"-               verbTest (\p -> patch p "") "PATCH"+               verbTest (`patch` "") "PATCH"+               verbTestGp get "GETPOST"+               verbTestGp (`post` "") "GETPOST"          it "can extract params from routes" $             get "/param-test/42" `shouldRespondWith` "int42" { matchStatus = 200 }          it "can handle multiple matching routes" $@@ -33,18 +35,19 @@             do get "/subcomponent/foo" `shouldRespondWith` "foo" { matchStatus = 200 }                get "/subcomponent/subcomponent2/bar" `shouldRespondWith` "bar" { matchStatus = 200 }          it "allows the definition of a fallback handler" $-            do get "/askldjas/aklsdj" `shouldRespondWith` "askldjas/aklsdj" { matchStatus = 200 }+            get "/askldjas/aklsdj" `shouldRespondWith` "askldjas/aklsdj" { matchStatus = 200 }          it "detected the preferred format" $-            do request "GET" "/preferred-format" [("Accept", "text/html,application/xml;q=0.9,image/webp,*/*;q=0.8")] "" `shouldRespondWith` "html" { matchStatus = 200 }+            request "GET" "/preferred-format" [("Accept", "text/html,application/xml;q=0.9,image/webp,*/*;q=0.8")] "" `shouldRespondWith` "html" { matchStatus = 200 }          it "/test-slash and test-noslash are the same thing" $             do get "/test-slash" `shouldRespondWith` "ok" { matchStatus = 200 }                get "test-slash" `shouldRespondWith` "ok" { matchStatus = 200 }                get "/test-noslash" `shouldRespondWith` "ok" { matchStatus = 200 }                get "test-noslash" `shouldRespondWith` "ok" { matchStatus = 200 }     where+      verbTestGp verb verbVerbose =+          verb "/verb-test-gp" `shouldRespondWith` (verbVerbose { matchStatus = 200 })       verbTest verb verbVerbose =-          (verb "/verb-test")-          `shouldRespondWith` (verbVerbose { matchStatus = 200 })+          verb "/verb-test" `shouldRespondWith` (verbVerbose { matchStatus = 200 })  actionSpec :: SpecWith Wai.Application actionSpec =
+ test/Web/Spock/Internal/SessionVaultSpec.hs view
@@ -0,0 +1,44 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TypeFamilies #-}+module Web.Spock.Internal.SessionVaultSpec (spec) where++import Web.Spock.Internal.SessionVault++import Control.Concurrent.STM+import Test.Hspec++data DummySession+    = DummySession+      { ds_key :: Int+      , ds_value :: Int+      } deriving (Show, Eq)++instance IsSession DummySession where+    type SessionKey DummySession = Int+    getSessionKey = ds_key++spec :: Spec+spec =+     describe "SessionVault" $+     do it "insert works correctly" $+           do let sess = DummySession 1 1+              vault <- atomically newSessionVault+              atomically $ storeSession sess vault+              atomically (loadSession (ds_key sess) vault) `shouldReturn` Just sess+        it "update works correctly" $+           do let sess = DummySession 1 1+                  sess2 = DummySession 1 2+              vault <- atomically newSessionVault+              atomically $ storeSession sess vault+              atomically (loadSession (ds_key sess) vault) `shouldReturn` Just sess+              atomically $ storeSession sess2 vault+              atomically (loadSession (ds_key sess) vault) `shouldReturn` Just sess2+        it "filter and toList works correctly" $+           do let sess = DummySession 1 1+                  sess2 = DummySession 2 2+                  sess3 = DummySession 3 3+              vault <- atomically newSessionVault+              atomically $ mapM_ (`storeSession` vault) [sess, sess2, sess3]+              atomically (toList vault) `shouldReturn` [sess, sess2, sess3]+              atomically $ filterSessions (\s -> ds_value s > 2) vault+              atomically (toList vault) `shouldReturn` [sess3]
test/Web/Spock/SafeSpec.hs view
@@ -14,6 +14,7 @@     do get root $ text "root"        get "verb-test" $ text "GET"        post "verb-test" $ text "POST"+       getpost "verb-test-gp" $ text "GETPOST"        put "verb-test" $ text "PUT"        delete "verb-test" $ text "DELETE"        patch "verb-test" $ text "PATCH"
test/Web/Spock/SimpleSpec.hs view
@@ -14,6 +14,7 @@     do get "/" $ text "root"        get "/verb-test" $ text "GET"        post "/verb-test" $ text "POST"+       getpost "/verb-test-gp" $ text "GETPOST"        put "/verb-test" $ text "PUT"        delete "/verb-test" $ text "DELETE"        patch "/verb-test" $ text "PATCH"@@ -21,13 +22,13 @@        get "/test-noslash" $ text "ok"        get "/param-test/:int" $            do Just (i :: Int) <- param "int"-              text $ "int" <> (T.pack $ show i)+              text $ "int" <> T.pack (show i)        get "/param-test/static" $            text "static"        subcomponent "/subcomponent" $          do get "foo" $ text "foo"             subcomponent "/subcomponent2" $-              do get "bar" $ text "bar"+              get "bar" $ text "bar"        get "/preferred-format" $          do fmt <- preferredFormat             case fmt of