packages feed

proto3-suite-0.3.0.0: src/Proto3/Suite/DhallPB.hs

{-# LANGUAGE FlexibleInstances #-}

{-# OPTIONS_GHC -fno-warn-orphans #-}

module Proto3.Suite.DhallPB
  ( -- * Modules
    module Dhall
  )
where
import           Data.Functor.Contravariant  (contramap)
import           Data.Int                    (Int32, Int64)
import           Data.Word                   (Word32, Word64)
import           Dhall                       (Inject (..), Interpret (..))
import           GHC.Float                   (double2Float, float2Double)
import           Proto3.Suite.Types          (Enumerated (..), Fixed (..))

import qualified Data.ByteString
import qualified Data.ByteString.Base64
import qualified Data.ByteString.Base64.Lazy
import qualified Data.ByteString.Lazy
import qualified Data.Map
import qualified Data.Text.Encoding
import qualified Data.Text.Lazy.Encoding
import qualified Dhall

--------------------------------------------------------------------------------
-- Interpret the special 'Enumerated' type

instance Dhall.Interpret a => Dhall.Interpret (Enumerated a)

instance Dhall.Interpret a => Dhall.Interpret (Either Int a)

--------------------------------------------------------------------------------
-- Interpret the strict and lazy ByteString types
--
-- We must base-64 decode a 'ByteString' after encoding it from a Text
-- because it may contain invalid UTF-8 data and Dhall does not have a
-- native type for bytes.

instance Dhall.Interpret Data.ByteString.Lazy.ByteString where
  autoWith _ = fmap b64Decode Dhall.lazyText
    where
      b64Decode = Data.ByteString.Base64.Lazy.decodeLenient . Data.Text.Lazy.Encoding.encodeUtf8

instance Dhall.Interpret Data.ByteString.ByteString where
  autoWith _ = fmap b64Decode Dhall.strictText
    where
      b64Decode =  Data.ByteString.Base64.decodeLenient . Data.Text.Encoding.encodeUtf8

--------------------------------------------------------------------------------
-- Interpret integer scalar types

-- Lossy interpretation and integer overflow can happen with the
-- following instances because the 'Dhall.Integer' (Dhall's only
-- integer type) equals Haskell's Integer type. We don't expect these
-- instances to introduce integer overflow because they should only
-- interpret Dhall rendered from protobuf messages created with
-- generated code.
--
-- TODO: we should perform run-time bounds-checking to at least hint
-- to the user that we interpreted something bad.

instance Dhall.Interpret Int where
  autoWith _ = fmap fromInteger Dhall.integer

instance Dhall.Interpret Int32 where
  autoWith _ = fmap fromInteger Dhall.integer

instance Dhall.Interpret Int64 where
  autoWith _ = fmap fromInteger Dhall.integer

instance Dhall.Interpret Word32 where
  autoWith _ = fmap fromIntegral Dhall.integer

instance Dhall.Interpret Word64 where
  autoWith _ = fmap fromIntegral Dhall.integer

instance Dhall.Interpret (Fixed Int32) where
  autoWith = fmap Fixed . Dhall.autoWith

instance Dhall.Interpret (Fixed Int64) where
  autoWith = fmap Fixed . Dhall.autoWith

instance Dhall.Interpret (Fixed Word32) where
  autoWith = fmap Fixed . Dhall.autoWith

instance Dhall.Interpret (Fixed Word64) where
  autoWith = fmap Fixed . Dhall.autoWith

--------------------------------------------------------------------------------
-- Interpret floating point scalar types
--
-- Loss of precision can happen when converting a 'Double' to a
-- 'Float'. We don't expect this instance to introduce loss of
-- precision because it should only interpret Dhall rendered from
-- protobuf messages created with generated code. The Dhall rendering
-- converts from a 'Float' to the 'Dhall.Double' type.

instance Dhall.Interpret Float where
  autoWith _ = fmap double2Float Dhall.double

--------------------------------------------------------------------------------
-- Interpret maps
--
-- Dhall has no map type.  We resort to an association list,
-- though that is not safe because keys may be repeated.

instance (Dhall.Interpret k, Dhall.Interpret v, Ord k) =>
         Dhall.Interpret (Data.Map.Map k v) where
  autoWith = fmap (fmap Data.Map.fromList) Dhall.autoWith

--------------------------------------------------------------------------------
-- Inject the special 'Enumerated' type

instance Dhall.Inject a => Dhall.Inject (Enumerated a)

instance Dhall.Inject a => Dhall.Inject (Either Int a)

--------------------------------------------------------------------------------
-- Inject integer scalar types

instance Dhall.Inject Int32 where
  injectWith = fmap (contramap toInteger) Dhall.injectWith

instance Dhall.Inject Int64 where
  injectWith = fmap (contramap toInteger) Dhall.injectWith

instance Dhall.Inject (Fixed Int32) where
  injectWith = fmap (contramap fixed) Dhall.injectWith

instance Dhall.Inject (Fixed Int64) where
  injectWith = fmap (contramap fixed) Dhall.injectWith

instance Dhall.Inject (Fixed Word32) where
  injectWith = fmap (contramap fixed) Dhall.injectWith

instance Dhall.Inject (Fixed Word64) where
  injectWith = fmap (contramap fixed) Dhall.injectWith

--------------------------------------------------------------------------------
-- Inject floating point scalar types

instance Dhall.Inject Float where
  injectWith = fmap (contramap float2Double) Dhall.injectWith

--------------------------------------------------------------------------------
-- Inject strict and lazy ByteStrings
--
-- We must base-64 encode a 'ByteString' before decoding it to a Text
-- because it may contain invalid UTF-8 data and Dhall does not have a
-- native type for bytes.

instance Dhall.Inject Data.ByteString.Lazy.ByteString where
  injectWith = fmap (contramap b64Encode) Dhall.injectWith
    where
      -- 'decodeUtf8' will throw an error on any invalid UTF-8 data
      -- but we should never encounter that case with this usage
      -- because we Base64 encode the ByteString first
      b64Encode = Data.Text.Lazy.Encoding.decodeUtf8 . Data.ByteString.Base64.Lazy.encode

instance Dhall.Inject Data.ByteString.ByteString where
  injectWith = fmap (contramap b64Encode) Dhall.injectWith
    where
      -- 'decodeUtf8' will throw an error on any invalid UTF-8 data
      -- but we should never encounter that case with this usage
      -- because we Base64 encode the ByteString first
      b64Encode = Data.Text.Encoding.decodeUtf8 . Data.ByteString.Base64.encode

--------------------------------------------------------------------------------
-- Inject maps
--
-- Dhall has no map type.  We resort to an association list,
-- though that is not safe because keys may be repeated.

instance (Dhall.Inject k, Dhall.Inject v) =>
         Dhall.Inject (Data.Map.Map k v) where
  injectWith = fmap (contramap Data.Map.toAscList) Dhall.injectWith