packages feed

hackport-0.5.1: cabal/Cabal/Distribution/Utils/BinaryWithFingerprint.hs

{-# LANGUAGE CPP #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE FlexibleContexts #-}
-- | Support for binary serialization with a fingerprint.
module Distribution.Utils.BinaryWithFingerprint (
    encodeWithFingerprint,
    decodeWithFingerprint,
    decodeWithFingerprintOrFailIO,
) where

#if MIN_VERSION_base(4,8,0)

import Distribution.Compat.Binary
import Data.ByteString.Lazy (ByteString)
import Control.Exception

import Data.Binary.Get
import Data.Binary.Put

import GHC.Generics
import GHC.Fingerprint
import Data.Typeable
import Control.Monad

-- | Private wrapper type so we can give 'Binary' instance for
-- 'Fingerprint'
newtype FP = FP Fingerprint

instance Binary FP where
    put (FP (Fingerprint a b)) = put a >> put b
    get = do
        a <- get
        b <- get
        return (FP (Fingerprint a b))

fingerprintRep :: forall a. Typeable (Rep a) => Proxy a -> Fingerprint
fingerprintRep _ = typeRepFingerprint (typeRep (Proxy :: Proxy (Rep a)))

-- | Encode a value, recording a fingerprint in the header.
--
-- The fingerprint is GHC's Typeable fingerprint associated with
-- the Generic Rep of a type: this fingerprint is better than
-- the fingerprint of the type itself, as it changes when the
-- representation changes (and thus the binary serialization format
-- changes.)
--
encodeWithFingerprint :: forall a. (Binary a, Typeable (Rep a)) => a -> ByteString
encodeWithFingerprint x = runPut $ do
    put (FP (fingerprintRep (Proxy :: Proxy a)))
    put x

-- | Decode a value, verifying the fingerprint in the header.
--
decodeWithFingerprint :: forall a. (Binary a, Typeable (Rep a)) => ByteString -> a
decodeWithFingerprint = runGet $ do
    FP fp <- get
    let expect_fp = fingerprintRep (Proxy :: Proxy a)
    when (expect_fp /= fp) $
        fail $ "Expected fingerprint " ++ show expect_fp ++
               " but got " ++ show fp
    get

-- | Decode a value, forcing the decoded value to discover decoding errors
-- and report them.
--
decodeWithFingerprintOrFailIO :: (Binary a, Typeable (Rep a)) => ByteString -> IO (Either String a)
decodeWithFingerprintOrFailIO bs =
  catch (evaluate (decodeWithFingerprint bs) >>= return . Right)
  $ \(ErrorCall str) -> return $ Left str

#else

import Distribution.Compat.Binary
import Data.ByteString.Lazy (ByteString)

-- Dummy implementations that don't actually save fingerprints

encodeWithFingerprint :: Binary a => a -> ByteString
encodeWithFingerprint = encode

decodeWithFingerprint :: Binary a => ByteString -> a
decodeWithFingerprint = decode

decodeWithFingerprintOrFailIO :: Binary a => ByteString -> IO (Either String a)
decodeWithFingerprintOrFailIO = decodeOrFailIO

#endif