packages feed

flashblast-0.0.4.0: src/FlashBlast/JSONFileStore.hs

{-# LANGUAGE UndecidableInstances #-}
module FlashBlast.JSONFileStore where

import Data.Aeson
import RIO
import qualified RIO.Map as Map
import Polysemy
import Polysemy.Error
import Polysemy.Input
import Polysemy.KVStore
import Path
import qualified UnliftIO.Path.Directory as U

newtype JSONFileStore = JSONFileStore (Path Rel File)
  deriving (Eq, Show, Generic)

newtype JSONParseException = JSONParseException String
  deriving (Show, Eq, Generic)

instance Exception JSONParseException where
  displayException (JSONParseException x) = x

eitherDecodeOrCreate :: (ToJSON a, FromJSON a, MonadIO m) => Path Rel 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)

runKVStoreAsJSONFileStore :: (Members '[Embed IO, Input JSONFileStore, Error JSONParseException] r,
                              FromJSONKey k, ToJSONKey k, FromJSON v, ToJSON v, Ord k)
                          => Sem (KVStore k v ': r) a -> Sem r a
runKVStoreAsJSONFileStore = interpret \case
  LookupKV k -> do
    JSONFileStore f <- input @JSONFileStore
    z <- embed $ eitherDecodeOrCreate f mempty
    case z of
      Left x  -> throw @JSONParseException $ JSONParseException x
      Right x -> return $ Map.lookup k x
  UpdateKV k v -> do
    JSONFileStore f <- input @JSONFileStore
    z <- embed $ eitherDecodeOrCreate f mempty
    case z of
      Left x -> throw $ JSONParseException x
      Right (x :: Map k v) -> embed $ encodeFile (toFilePath f) (Map.alter (const v) k x)