packages feed

freckle-app-1.0.3.0: library/Freckle/App/Memcached/CacheKey.hs

module Freckle.App.Memcached.CacheKey
  ( CacheKey
  , cacheKey
  , cacheKeyThrow
  , fromCacheKey
  ) where

import Freckle.App.Prelude

import Data.Char (isControl, isSpace)
import qualified Data.Text as T
import Database.Memcache.Types (Key)
import GHC.Stack (HasCallStack)
import UnliftIO.Exception (throwString)

newtype CacheKey = CacheKey Text
  deriving stock Show
  deriving newtype (Eq, Hashable)

unCacheKey :: CacheKey -> Text
unCacheKey (CacheKey x) = x

-- | Build a 'CacheKey', ensuring it's valid for Memcached
--
-- <https://github.com/memcached/memcached/blob/master/doc/protocol.txt#L41>
--
-- @
-- Currently the length limit of a key is set at 250 characters (of course,
-- normally clients wouldn't need to use such long keys); the key must not
-- include control characters or whitespace.
-- @
--
cacheKey :: Text -> Either String CacheKey
cacheKey t
  | T.length t > 250 = invalid "Must be fewer than 250 characters"
  | T.any isControl t = invalid "Cannot contain control characters"
  | T.any isSpace t = invalid "Cannot container whitespace"
  | otherwise = Right $ CacheKey t
 where
  invalid msg =
    Left $ "Not a valid memcached key:\n  " <> unpack t <> "\n\n" <> msg

-- | Build a 'CacheKey' and throw if invalid
cacheKeyThrow :: (HasCallStack, MonadIO m) => Text -> m CacheKey
cacheKeyThrow = either throwString pure . cacheKey

fromCacheKey :: CacheKey -> Key
fromCacheKey = encodeUtf8 . unCacheKey