{-# LANGUAGE BlockArguments #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE GADTs #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE PolyKinds #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeApplications #-} {-# LANGUAGE TypeOperators #-} module Polysemy.JSONFileKVStore ( JSONParseException (..), runKVStoreAsJSONFileStore, eitherDecodeOrCreate, ) where import Control.Monad.Catch import Control.Monad.Extra import Control.Monad.IO.Class import Data.Aeson import Data.Map (Map) import qualified Data.Map as Map import GHC.Generics import Path import Polysemy import Polysemy.Error import Polysemy.KVStore import qualified UnliftIO.Path.Directory as U newtype JSONParseException = JSONParseException String deriving (Show, Eq, Generic) instance Exception JSONParseException where displayException (JSONParseException x) = x eitherDecodeOrCreate :: (ToJSON a, FromJSON a, MonadIO m) => Path b File -> a -> m (Either String a) eitherDecodeOrCreate f x = do whenM (fmap not . U.doesFileExist $ f) $ liftIO $ encodeFile (toFilePath f) x liftIO $ eitherDecodeFileStrict' (toFilePath f) -- | Run a `KVStore` as a json file stored as a `Map`. -- -- @since 0.1.0.0 runKVStoreAsJSONFileStore :: ( Members '[Embed IO, Error JSONParseException] r, FromJSONKey k, ToJSONKey k, FromJSON v, ToJSON v, Ord k ) => Path b File -> Sem (KVStore k v ': r) a -> Sem r a runKVStoreAsJSONFileStore d = interpret \case LookupKV k -> do z <- eitherDecodeOrCreate d mempty case z of Left x -> throw @JSONParseException $ JSONParseException x Right x -> return $ Map.lookup k x UpdateKV k v -> do z <- eitherDecodeOrCreate d mempty case z of Left x -> throw $ JSONParseException x Right (x :: Map k v) -> embed $ encodeFile (toFilePath d) (Map.alter (const v) k x) {-# INLINE runKVStoreAsJSONFileStore #-}