keyed-vals-0.2.0.0: src/KeyedVals/Handle/Codec.hs
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE OverloadedStrings #-}
{-# OPTIONS_HADDOCK prune not-home #-}
{- |
Copyright : (c) 2018-2022 Tim Emiola
SPDX-License-Identifier: BSD3
Maintainer : Tim Emiola <tim@emio.la>
Provides a typeclass that converts types to and from keys or vals and
combinators that help it to encode data using 'Handle'
This serves to decouple the encoding/decoding, making it straightforward to use
the typed interface in 'KeyedVals.Handle.Typed' with a wide set of
encoding/decoding schemes
-}
module KeyedVals.Handle.Codec (
-- * decode/encode support
EncodeKV (..),
DecodeKV (..),
decodeOr,
decodeOr',
decodeOrGone,
decodeOrGone',
-- * decode encoded @ValsByKey@
decodeKVs,
-- * save encoded @ValsByKey@ using a @Handle@
saveEncodedKVs,
updateEncodedKVs,
-- * error conversion
FromHandleErr (..),
) where
import Data.Bifunctor (bimap)
import Data.Map.Strict (Map)
import qualified Data.Map.Strict as Map
import Data.Text (Text)
import KeyedVals.Handle
-- | Specifies how type @a@ encodes as a @Key@ or a @Val@.
class EncodeKV a where
encodeKV :: a -> Val
-- | Specifies how type @a@ can be decoded from a @Key@ or a @Val@.
class DecodeKV a where
decodeKV :: Val -> Either Text a
-- | Specifies how to turn 'HandleErr' into a custom error type @err@.
class FromHandleErr err where
fromHandleErr :: HandleErr -> err
instance FromHandleErr HandleErr where
fromHandleErr = id
-- | Like 'decodeOr', but transforms 'Nothing' to 'Gone'.
decodeOrGone ::
(DecodeKV b, FromHandleErr err) =>
Key ->
Maybe Val ->
Either err b
decodeOrGone key x =
case decodeOr x of
Left err -> Left err
Right mb -> maybe (Left $ fromHandleErr $ Gone key) Right mb
-- | Like 'decodeOr'', but transforms 'Nothing' to 'Gone'.
decodeOrGone' ::
(DecodeKV b, FromHandleErr err) =>
Key ->
Either err (Maybe Val) ->
Either err b
decodeOrGone' key = either Left $ decodeOrGone key
-- | Decode a value, transformi decode errors to type @err@.
decodeOr' ::
(DecodeKV b, FromHandleErr err) =>
Either err (Maybe Val) ->
Either err (Maybe b)
decodeOr' = either Left decodeOr
-- | Decode a value, transforming decode errors to type @err@.
decodeOr ::
(DecodeKV a, FromHandleErr err) =>
Maybe Val ->
Either err (Maybe a)
decodeOr = maybe (pure Nothing) (fmap Just . firstEither notDecoded . decodeKV)
notDecoded :: FromHandleErr err => Text -> err
notDecoded = fromHandleErr . NotDecoded
decode' :: (FromHandleErr err, DecodeKV a) => Val -> Either err a
decode' = either (Left . notDecoded) Right . decodeKV
-- | Decodes a 'Map' from a @ValsByKey@ with encoded @Keys@ and @Vals@.
decodeKVs ::
(Ord a, DecodeKV a, DecodeKV b, FromHandleErr err) =>
ValsByKey ->
Either err (Map a b)
decodeKVs =
let step _ _ (Left x) = Left x
step k v (Right m) = case (decode' k, decode' v) of
(Left x, _) -> Left x
(_, Left y) -> Left y
(Right k', Right v') -> Right $ Map.insert k' v' m
in Map.foldrWithKey step (Right Map.empty)
-- | Like 'saveEncodedKVs', but updates the keys rather than completely replacing it.
updateEncodedKVs ::
(Ord a, EncodeKV a, EncodeKV b, Monad m, FromHandleErr err) =>
Handle m ->
Key ->
Map a b ->
m (Either err ())
updateEncodedKVs = saveOrUpdateKVs True
{- | Encode a 'Map' as a 'ValsByKey' with the @'Key's@ and @'Val's@ encoded.
- 'HandleErr' may be transformed to different error type
-}
saveEncodedKVs ::
(Ord a, EncodeKV a, EncodeKV b, Monad m, FromHandleErr err) =>
Handle m ->
Key ->
Map a b ->
m (Either err ())
saveEncodedKVs = saveOrUpdateKVs False
-- | Encode any 'Map' as a 'ValsByKey' by encoding its @'Key's@ and @'Val's@.
saveOrUpdateKVs ::
(Ord a, EncodeKV a, EncodeKV b, Monad m, FromHandleErr err) =>
-- | when @True@, the dict is updated
Bool ->
Handle m ->
Key ->
Map a b ->
m (Either err ())
saveOrUpdateKVs _ _ _ kvs | Map.size kvs == 0 = pure $ Right ()
saveOrUpdateKVs update h key dict =
let asRemote =
Map.fromList
. fmap (bimap encodeKV encodeKV)
. Map.toList
saver = if update then (updateKVs h) else (saveKVs h)
in fmap (firstEither fromHandleErr) $ saver key $ asRemote dict
firstEither :: (err1 -> err2) -> Either err1 b -> Either err2 b
firstEither f = either (Left . f) Right