packages feed

caching-vault (empty) → 0.1.0.0

raw patch · 6 files changed

+272/−0 lines, 6 filesdep +basedep +caching-vaultdep +hspecsetup-changed

Dependencies added: base, caching-vault, hspec, stm, stm-containers, text, time, timespan

Files

+ LICENSE view
@@ -0,0 +1,30 @@+Copyright Alexander Thiemann (c) 2021++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++    * Redistributions of source code must retain the above copyright+      notice, this list of conditions and the following disclaimer.++    * Redistributions in binary form must reproduce the above+      copyright notice, this list of conditions and the following+      disclaimer in the documentation and/or other materials provided+      with the distribution.++    * Neither the name of Alexander Thiemann nor the names of other+      contributors may be used to endorse or promote products derived+      from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ README.md view
@@ -0,0 +1,23 @@+# caching-vault++A simple [vault](https://hackage.haskell.org/package/vault) style cache implementation based on [stm-containers](https://hackage.haskell.org/package/stm-containers).++## Example++``` haskell+import Data.Time+import qualified Data.Cache.Vault as C++main :: IO ()+main =+  do cache <- C.newCache+     let key :: C.Key String+         key = C.mintLabeledKey "foo"+     C.insert key Nothing "cached value" cache+     +     now <- getCurrentTime+     value <- C.lookup now key cache+     case value of+       Nothing -> putStrLn "Cache miss"+       Just val -> putStrLn ("Cache value is: " <> val)+```
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ caching-vault.cabal view
@@ -0,0 +1,64 @@+cabal-version: 1.12++-- This file has been generated from package.yaml by hpack version 0.33.0.+--+-- see: https://github.com/sol/hpack+--+-- hash: d045485a8c1101cb9c1d54251cc48c42e544e597c1ec7c06b0e0a5850a78ddc8++name:           caching-vault+version:        0.1.0.0+synopsis:       A vault-style cache implementation+description:    Allows a central cache for arbitrary values with expiry dates+category:       Data+homepage:       https://github.com/agrafix/caching-vault#readme+bug-reports:    https://github.com/agrafix/caching-vault/issues+author:         Alexander Thiemann+maintainer:     Alexander Thiemann <mail@thiemann.at>+copyright:      2021 Alexander Thiemann+license:        BSD3+license-file:   LICENSE+build-type:     Simple+extra-source-files:+    README.md++source-repository head+  type: git+  location: https://github.com/agrafix/caching-vault++library+  exposed-modules:+      Data.Cache.Vault+  other-modules:+      Paths_caching_vault+  hs-source-dirs:+      src+  default-extensions: OverloadedStrings DataKinds TypeOperators TypeFamilies GADTs FlexibleInstances FlexibleContexts MultiParamTypeClasses StrictData ScopedTypeVariables DeriveGeneric DeriveFunctor+  ghc-options: -Wall -fwarn-tabs -fwarn-incomplete-uni-patterns -fwarn-incomplete-record-updates+  build-depends:+      base >=4.7 && <5+    , stm+    , stm-containers+    , text+    , time+  default-language: Haskell2010++test-suite caching-vault-test+  type: exitcode-stdio-1.0+  main-is: Spec.hs+  other-modules:+      Paths_caching_vault+  hs-source-dirs:+      test+  default-extensions: OverloadedStrings DataKinds TypeOperators TypeFamilies GADTs FlexibleInstances FlexibleContexts MultiParamTypeClasses StrictData ScopedTypeVariables DeriveGeneric DeriveFunctor+  ghc-options: -Wall -fwarn-tabs -fwarn-incomplete-uni-patterns -fwarn-incomplete-record-updates -threaded -rtsopts -with-rtsopts=-N+  build-depends:+      base >=4.7 && <5+    , caching-vault+    , hspec+    , stm+    , stm-containers+    , text+    , time+    , timespan+  default-language: Haskell2010
+ src/Data/Cache/Vault.hs view
@@ -0,0 +1,96 @@+module Data.Cache.Vault+  ( Cache, newCache+  , Key, mintLabeledKey, mintUniqKey+  , insert, delete, reset, lookup+  )+where++import Control.Concurrent.STM (atomically)+import Data.IORef+import Data.Time+import Data.Typeable+import GHC.Exts+import GHC.Fingerprint+import Prelude hiding (lookup)+import System.IO.Unsafe (unsafePerformIO)+import Unsafe.Coerce (unsafeCoerce)+import qualified Data.Text as T+import qualified StmContainers.Map as M++type KeyRepr = T.Text++data CacheEntry+  = CacheEntry+  { ceValidUntil :: Maybe UTCTime+  , ceValue :: Any+  }++newtype Cache+  = Cache { _unCache :: M.Map KeyRepr CacheEntry }++newtype Key a+  = Key { unKey :: KeyRepr }+  deriving (Show, Eq)++keyCounter :: IORef Int+keyCounter =+  unsafePerformIO $ newIORef 0+{-# NOINLINE keyCounter #-}++-- | Mint a globally unique key+mintUniqKey :: IO (Key a)+mintUniqKey =+  atomicModifyIORef' keyCounter $ \ctr ->+  ( ctr + 1+  , Key $ "uniq/" <> T.pack (show ctr)+  )++-- | Mint a key with a label for a given type. Note that keys+-- with the same label but for different types are different.+mintLabeledKey :: forall a. Typeable a => T.Text -> Key a+mintLabeledKey label =+  Key $ "label/" <> label <> "/" <> typeSig+  where+    typeSig =+      let (Fingerprint x1 x2) =+            typeRepFingerprint (typeRep (Proxy :: Proxy (Proxy a)))+      in T.pack $ show x1 <> "." <> show x2++-- | Create a new cache container.+newCache :: IO Cache+newCache =+  Cache <$> M.newIO++-- | Insert a value into the cache with an optional expiry date.+insert :: Key a -> Maybe UTCTime -> a -> Cache -> IO ()+insert k t v (Cache ref) =+  atomically $ M.insert val key ref+  where+    val =+      CacheEntry+      { ceValidUntil = t+      , ceValue = unsafeCoerce v+      }+    key = unKey k++-- | Delete a value from the cache.+delete :: Key a -> Cache -> IO ()+delete k (Cache ref) =+  atomically $ M.delete (unKey k) ref++-- | Purge all values form the cache.+reset :: Cache -> IO ()+reset (Cache ref) =+  atomically $ M.reset ref++-- | Given the current time, lookup a key in the cache.+lookup :: UTCTime -> Key a -> Cache -> IO (Maybe a)+lookup now k (Cache ref) =+  do entry <-+       atomically $ M.lookup (unKey k) ref+     case entry of+       Nothing -> pure Nothing+       Just e ->+         case ceValidUntil e of+           Just validUntil | validUntil < now -> pure Nothing+           _ -> pure (Just $ unsafeCoerce (ceValue e))
+ test/Spec.hs view
@@ -0,0 +1,57 @@+import Test.Hspec++import Data.Time+import Data.Time.TimeSpan++import Control.Monad (replicateM)+import Data.List (nub)+import qualified Data.Cache.Vault as C++main :: IO ()+main =+  hspec $+  do it "allows reading previously written values" $+       do now <- getCurrentTime+          cache <- C.newCache+          let key = C.mintLabeledKey "foo"+          C.insert key Nothing False cache+          C.lookup now key cache `shouldReturn` Just False+     it "expires values" $+       do now <- getCurrentTime+          cache <- C.newCache+          let key = C.mintLabeledKey "foo"+          C.insert key (Just now) False cache+          C.lookup (addUTCTimeTS (minutes 1) now) key cache `shouldReturn` Nothing+     it "deletes values" $+       do now <- getCurrentTime+          cache <- C.newCache+          let key = C.mintLabeledKey "foo"+          C.insert key Nothing False cache+          C.delete key cache+          C.lookup now key cache `shouldReturn` Nothing+     it "purges values" $+       do now <- getCurrentTime+          cache <- C.newCache+          let key = C.mintLabeledKey "foo"+          C.insert key Nothing False cache+          C.reset cache+          C.lookup now key cache `shouldReturn` Nothing+     it "generates distinct unique keys" $+       do keys <- replicateM 200 C.mintUniqKey+          keys `shouldBe` nub keys+     it "differentiates labeled and unique keys" $+       do let key :: C.Key Bool+              key = C.mintLabeledKey "foo"+          uniqKey <- C.mintUniqKey+          key `shouldNotBe` uniqKey+     it "keys with same label of different types are different" $+       do now <- getCurrentTime+          cache <- C.newCache+          let key1 :: C.Key Bool+              key1 = C.mintLabeledKey "foo"++              key2 :: C.Key Int+              key2 = C.mintLabeledKey "foo"+          C.insert key1 Nothing False cache+          C.lookup now key1 cache `shouldReturn` Just False+          C.lookup now key2 cache `shouldReturn` Nothing