packages feed

yesod-session-persist-0.0.0.0: internal/Yesod/Session/Embedding/Map.hs

module Yesod.Session.Embedding.Map
  ( SessionMapEmbedding
  , MapOperations (..)
  , bsKeyEmbedding
  , showReadKeyEmbedding
  ) where

import Internal.Prelude

import Control.Monad.State (StateT (..))
import Control.Monad.State qualified as State
import Data.Map.Strict qualified as Map
import Data.Text qualified as T
import Data.Text.Encoding (decodeUtf8', encodeUtf8)
import Embedding
import Yesod.Core (HandlerFor, deleteSession, lookupSessionBS, setSessionBS)

-- | Specifies how we represent some value within a 'SessionMap'
--
-- We use this to sort of abuse the session; key rotation and freezing are
-- done by embedding special values among the session data. These special
-- values are extracted from the map before persisting to storage and are
-- never actually saved.
type SessionMapEmbedding a = Embedding (MapOperations Text ByteString) () a

-- | A monadic context with operations over some 'Map'-like state
--
-- This allows us to generalize between pure operations over 'Map' and
-- the more limited session manipulation utilities afforded by Yesod.
-- (See the instance list for this class.)
class (Monad m, Ord k) => MapOperations k v m | m -> k v where
  lookup :: k -> m (Maybe v)
  assign :: k -> Maybe v -> m ()

instance MapOperations Text ByteString (HandlerFor site) where
  lookup k = lookupSessionBS k
  assign k v = maybe (deleteSession k) (setSessionBS k) v

instance (Monad m, Ord k) => MapOperations k v (StateT (Map k v) m) where
  lookup k = State.gets $ Map.lookup k
  assign k v = State.modify' $ Map.alter (const v) k

-- | An embedding which stores a value at some particular key in a map-like structure
bsKeyEmbedding :: k -> Embedding (MapOperations k a) e a
bsKeyEmbedding key =
  Embedding
    { embed = assign key
    , extract = fmap Right $ lookup key <* assign key Nothing
    }

-- | Represents a value in a 'SessionMap' by storing the
--   UTF-8 encoding of its 'show' representation at the given key
showReadKeyEmbedding
  :: (Read a, Show a) => k -> Embedding (MapOperations k ByteString) () a
showReadKeyEmbedding k =
  dimapEmbedding
    ( maybe (throwError ()) pure
        . readMaybe
        <=< (bimap (const ()) T.unpack . decodeUtf8')
    )
    (encodeUtf8 . T.pack . show)
    (bsKeyEmbedding k)