packages feed

sr-extra-1.85: Extra/SafeCopy.hs

-- | This module exports a template haskell function to create
-- Serialize instances based on the SafeCopy instance, and an
-- alternative decode function that puts the decode type in the error
-- message.  It also re-exports all other Data.Serialize symbols

{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE TemplateHaskell #-}

module Extra.SafeCopy
    ( module Data.SafeCopy
    , DecodeError(..)
    , encodeSafe
    , decodeAllSafe
#if 0
    , decodeMSafe
    , decodeMSafe'
#endif
    , decodePrismSafe
    , encodeGetterSafe
    ) where

import Control.Exception (ErrorCall(..))
import Control.Lens (Getter, Prism', prism, re)
import Control.Monad.Catch (catch, MonadCatch)
import Control.Monad.Except (MonadError)
import Data.ByteString as B (ByteString, null)
import Data.Data (Proxy(Proxy), Typeable)
import Data.SafeCopy (base, SafeCopy, safeGet, safePut)
#if !MIN_VERSION_base(4,11,0)
import Data.Semigroup (Semigroup((<>)))
#endif
import Data.Serialize hiding (decode, encode)
import Data.UUID.Orphans ()
--import Extra.Errors (Member, OneOf, throwMember)
import Extra.Orphans ()
import Extra.Serialize (DecodeError(..), fakeTypeRep)

encodeSafe :: SafeCopy a => a -> ByteString
encodeSafe = runPut . safePut

-- Version of decode that errors if all input is not consumed.
decodeAllSafe :: forall a. (SafeCopy a) => ByteString -> Either String a
decodeAllSafe b =
  case runGetState safeGet b 0 of
    Left s -> Left s
    Right (a, more) | B.null more -> Right a
    Right (_, more) -> Left ("decode " <> show b <> " failed to consume " <> show more)

#if 0
-- | Monadic version of decode.
decodeMSafe ::
  forall a e m. (SafeCopy a, Typeable a, Member DecodeError e, MonadError (OneOf e) m)
  => ByteString
  -> m a
decodeMSafe bs =
  case decodeAllSafe bs of
    Left s -> throwMember (DecodeError bs (fakeTypeRep (Proxy @a)) s)
    Right a -> return a

-- | Like 'decodeM', but also catches any ErrorCall thrown and lifts
-- it into the MonadError instance.  I'm not sure whether this can
-- actually happen.  What I'm seeing is probably an error call from
-- outside the serialize package, in which case this (and decode') are
-- pointless.
decodeMSafe' ::
  forall e m a. (SafeCopy a, Typeable a, Member DecodeError e, MonadError (OneOf e) m, MonadCatch m)
  => ByteString
  -> m a
decodeMSafe' bs = go `catch` handle
  where
    go = case decodeAllSafe bs of
           Left s -> throwMember (DecodeError bs (fakeTypeRep (Proxy @a)) s)
           Right a -> return a
    handle :: ErrorCall -> m a
    handle (ErrorCall s) = throwMember (DecodeError bs (fakeTypeRep (Proxy @a)) s)
#endif

-- | Serialize/deserialize prism.
decodePrismSafe :: forall a. (SafeCopy a) => Prism' ByteString a
decodePrismSafe = prism encodeSafe (\s -> either (\_ -> Left s) Right (decodeAllSafe s :: Either String a))

-- | Inverting a prism turns it into a getter.
encodeGetterSafe :: forall a. (SafeCopy a) => Getter a ByteString
encodeGetterSafe = re decodePrismSafe