packages feed

mig-0.2.0.0: src/Mig/Core/Server/Cache.hs

-- | LRU cache to speedup fetching of the route handler
module Mig.Core.Server.Cache (
  CacheConfig (..),
  CacheKey (..),
  CacheValue (..),
  RouteCache (..),
  newRouteCache,
  withCache,
) where

import Control.Monad
import Control.Monad.IO.Class
import Data.ByteString (ByteString)
import Data.Cache.LRU.IO (AtomicLRU)
import Data.Cache.LRU.IO qualified as Lru
import Data.Text (Text)
import Mig.Core.Api (CaptureMap)
import Mig.Core.Class.Route (Route)
import Network.HTTP.Types.Method (Method)

-- | Cache config
data CacheConfig = CacheConfig
  { size :: Int
    -- ^ how many items are allowed in the cache
  , cacheFilter :: CacheKey -> Bool
    -- ^ which route to cache
  }

-- | Route key identidfies the single item for caching
data CacheKey = CacheKey
  { inputType :: ByteString  -- ^ value of "Content-Type" header
  , outputType :: ByteString -- ^ value of "Accept" header
  , method :: Method -- ^ http method
  , path :: [Text] -- ^ path to route (includes inlined captures)
  }
  deriving (Show, Eq, Ord)

-- | Cache value
data CacheValue m = CacheValue
  { captures :: CaptureMap -- ^ extracted capture map from the path
  , route :: Route m -- ^ route handler
  }

-- | Route cache
data RouteCache m = RouteCache
  { cacheFilter :: CacheKey -> Bool
    -- ^ which route to cache (if True the route is cached)
  , cache :: AtomicLRU CacheKey (CacheValue m)
    -- ^ cache map
  }

-- | Allocates new cache
newRouteCache :: CacheConfig -> IO (RouteCache m)
newRouteCache config =
  RouteCache config.cacheFilter <$> Lru.newAtomicLRU (Just (fromIntegral config.size))

-- | Caches the function of route finder
withCache :: RouteCache m -> (CacheKey -> Maybe (CacheValue m)) -> CacheKey -> IO (Maybe (CacheValue m))
withCache (RouteCache cacheFilter cache) f key = do
  mCacheResult <- liftIO $ Lru.lookup key cache
  case mCacheResult of
    Just result -> pure (Just result)
    Nothing -> do
      case f key of
        Just result -> do
          when (cacheFilter key) $ Lru.insert key result cache
          pure (Just result)
        Nothing -> pure Nothing