packages feed

proto3-suite-0.7.0: src/Proto3/Suite/Class.hs

-- | This module provides type classes for encoding and decoding protocol
-- buffers message, as well as a safer alternative to the raw 'Proto3.Wire'
-- library based on 'GHC.Generics'.
--
-- = Classes
--
-- The 'Primitive' class captures those types which correspond to primitive field
-- types, as defined by the protocol buffers specification. A 'Primitive' type is
-- one which can always be encoded as a single key/value pair in the wire format.
--
-- The 'MessageField' class captures those types which are encoded under a single
-- key in the wire format, i.e. primitives, packed and unpacked lists, and
-- embedded messages.
--
-- The 'Message' class captures types which correspond to protocol buffers messages.
-- Instances of 'Message' can be written by hand for your types by using the
-- functions in the 'Proto3.Suite.Encode' and 'Proto3.Suite.Decode'
-- modules. In the case where the message format is determined by your Haskell code,
-- you might prefer to derive your 'Message' instances using generic deriving.
--
-- = Generic Instances
--
-- Using the 'GHC.Generics' approach, instead of generating Haskell code from a
-- .proto file, we write our message formats as Haskell types, and generate a
-- serializer/deserializer pair.
--
-- To use this library, simply derive a 'Generic' instance for your type(s), and
-- use the default `Message` instance.
--
-- For generic 'Message' instances, field numbers are automatically generated,
-- starting at 1. Therefore, adding new fields is a compatible change only at the
-- end of a record. Renaming fields is also safe. You should not use the generic
-- instances if you are starting from an existing .proto file.
--
-- = Strings
--
-- Use 'TL.Text' instead of 'String' for string types inside messages.
--
-- = Example
--
-- > data MultipleFields =
-- >   MultipleFields { multiFieldDouble :: Double
-- >                  , multiFieldFloat  :: Float
-- >                  , multiFieldInt32  :: Int32
-- >                  , multiFieldInt64  :: Int64
-- >                  , multiFieldString :: TL.Text
-- >                  , multiFieldBool   :: Bool
-- >                  } deriving (Show, Generic, Eq)
-- >
-- > instance Message MultipleFields
-- >
-- > serialized = toLazyByteString $ MultipleFields 1.0 1.0 1 1 "hi" True
-- >
-- > deserialized :: MultipleFields
-- > deserialized = case parse (toStrict serialized) of
-- >                  Left e -> error e
-- >                  Right msg -> msg

{-# LANGUAGE AllowAmbiguousTypes        #-}
{-# LANGUAGE BangPatterns               #-}
{-# LANGUAGE CPP                        #-}
{-# LANGUAGE DataKinds                  #-}
{-# LANGUAGE DefaultSignatures          #-}
{-# LANGUAGE DerivingVia                #-}
{-# LANGUAGE FlexibleContexts           #-}
{-# LANGUAGE FlexibleInstances          #-}
{-# LANGUAGE MagicHash                  #-}
{-# LANGUAGE OverloadedStrings          #-}
{-# LANGUAGE PolyKinds                  #-}
{-# LANGUAGE ScopedTypeVariables        #-}
{-# LANGUAGE StandaloneDeriving         #-}
{-# LANGUAGE TypeApplications           #-}
{-# LANGUAGE TypeFamilies               #-}
{-# LANGUAGE TypeOperators              #-}
{-# LANGUAGE UndecidableInstances       #-}

module Proto3.Suite.Class
  ( Primitive(..)
  , MessageField(..)
  , Message(..)

  -- * Encoding
  , toLazyByteString

  -- * Decoding
  , HasDefault(..)
  , fromByteString
  , fromB64
  , coerceOver
  , unsafeCoerceOver

  -- * Documentation
  , Named(..)
  , Finite(..)
  , message
  , Proto3.Suite.Class.enum

  -- * Generic Classes
  , GenericMessage(..)
  ) where

import           Control.Applicative
import           Control.Monad
import qualified Data.ByteString        as B
import qualified Data.ByteString.Base64 as B64
import qualified Data.ByteString.Lazy   as BL
import           Data.Coerce            (Coercible, coerce)
import qualified Data.Foldable          as Foldable
import           Data.Functor           (($>))
import           Data.Int               (Int32, Int64)
import           Data.Kind              (Type)
import qualified Data.Map               as M
import           Data.Maybe             (fromMaybe, isNothing)
import           Data.Proxy             (Proxy (..))
import           Data.String            (IsString (..))
import qualified Data.Text              as T
import qualified Data.Text.Lazy         as TL
import qualified Data.Text.Short        as TS
import qualified Data.Traversable       as TR
import           Data.Vector            (Vector)
import           Data.Word              (Word32, Word64)
import           GHC.Exts               (fromList, Proxy#, proxy#)
import           GHC.Generics
import           GHC.TypeLits
import           Google.Protobuf.Wrappers.Polymorphic (Wrapped(..))
import           Proto3.Suite.DotProto
import qualified Proto3.Suite.Types
import           Proto3.Suite.Types     hiding (Bytes, String)
import           Proto3.Wire
import           Proto3.Wire.Decode     (ParseError, Parser (..), RawField,
                                         RawMessage, RawPrimitive, runParser)
import qualified Proto3.Wire.Decode     as Decode
import qualified Proto3.Wire.Encode     as Encode
import           Unsafe.Coerce          (unsafeCoerce)

#ifdef LARGE_RECORDS
import qualified Data.Record.Generic as LG
import qualified Data.Record.Generic.GHC as LG
import qualified Data.Record.Generic.Rep as LG
#endif

-- | Pass through those values that are outside the enum range;
-- this is for forward compatibility as enumerations are extended.
codeFromEnumerated :: ProtoEnum e => Enumerated e -> Int32
codeFromEnumerated = either id fromProtoEnum . enumerated
{-# INLINE codeFromEnumerated #-}

-- | Values inside the enum range are in Right, the rest in Left;
-- this is for forward compatibility as enumerations are extended.
codeToEnumerated :: ProtoEnum e => Int32 -> Enumerated e
codeToEnumerated code =
  Enumerated $ maybe (Left code) Right (toProtoEnumMay code)
{-# INLINE codeToEnumerated #-}

-- | A class for types with default values per the protocol buffers spec.
class HasDefault a where
  -- | The default value for this type.
  def :: a

  default def :: (Generic a, GenericHasDefault (Rep a)) => a
  def = to (genericDef @(Rep a))

  isDefault :: a -> Bool

  default isDefault :: Eq a => a -> Bool
  isDefault = (== def)

-- | Do not encode the default value
omittingDefault
  :: HasDefault a
  => (a -> Encode.MessageBuilder)
  -> a
  -> Encode.MessageBuilder
omittingDefault f p
  | isDefault p = mempty
  | otherwise = f p

-- -- | Numeric types default to zero
-- instance Num a => HasDefault a where def = 0

instance HasDefault Int where def = 0
instance HasDefault Integer where def = 0

instance HasDefault Int32 where def = 0
instance HasDefault Int64 where def = 0
instance HasDefault Word32 where def = 0
instance HasDefault Word64 where def = 0
instance HasDefault (Signed Int32) where def = 0
instance HasDefault (Signed Int64) where def = 0
-- | Used in generated records to represent @sfixed32@
instance HasDefault (Fixed Int32) where def = 0
-- | Used in generated records to represent @sfixed64@
instance HasDefault (Fixed Int64) where def = 0
instance HasDefault (Fixed Word32) where def = 0
instance HasDefault (Fixed Word64) where def = 0
instance HasDefault (Signed (Fixed Int32)) where def = 0
instance HasDefault (Signed (Fixed Int64)) where def = 0
instance HasDefault Float where def = 0
instance HasDefault Double where def = 0

instance HasDefault Bool where
  def = False

instance HasDefault T.Text where
  def = mempty

deriving via T.Text instance HasDefault (Proto3.Suite.Types.String T.Text)

instance HasDefault TL.Text where
  def = mempty

deriving via TL.Text instance HasDefault (Proto3.Suite.Types.String TL.Text)

instance HasDefault TS.ShortText where
  def = mempty

deriving via TS.ShortText instance HasDefault (Proto3.Suite.Types.String TS.ShortText)

instance HasDefault B.ByteString where
  def = mempty

deriving via B.ByteString instance HasDefault (Proto3.Suite.Types.Bytes B.ByteString)

instance HasDefault BL.ByteString where
  def = mempty

deriving via BL.ByteString instance HasDefault (Proto3.Suite.Types.Bytes BL.ByteString)

instance ProtoEnum e => HasDefault (Enumerated e) where
  def = codeToEnumerated 0
  isDefault = (== 0) . codeFromEnumerated

deriving via (a :: Type) instance HasDefault a => HasDefault (Wrapped a)

instance HasDefault (UnpackedVec a) where
  def = mempty
  isDefault = null . unpackedvec

instance HasDefault (PackedVec a) where
  def = mempty
  isDefault = null . packedvec

instance HasDefault (NestedVec a) where
  def = mempty
  isDefault = null . nestedvec

instance HasDefault (Nested a) where
  def = Nested Nothing
  isDefault = isNothing . nested

instance (HasDefault a) => HasDefault (ForceEmit a) where
  def       = ForceEmit def
  isDefault = isDefault . forceEmit

-- | Used in fields of generated records to represent an unwrapped
-- 'PackedVec'/'UnpackedVec'
instance HasDefault (Vector a) where
  def       = mempty
  isDefault = null

-- | Used in generated records to represent an unwrapped 'Nested'
instance HasDefault (Maybe a) where
  def       = Nothing
  isDefault = isNothing

instance HasDefault (M.Map k v) where
  def = M.empty
  isDefault = M.null

class GenericHasDefault (f :: Type -> Type) where
  genericDef :: f x
instance HasDefault f => GenericHasDefault (K1 i f) where
  genericDef = K1 (def @f)
instance (GenericHasDefault f, GenericHasDefault g) => GenericHasDefault (f :*: g) where
  genericDef = genericDef @f :*: genericDef @g
instance (GenericHasDefault f, GenericHasDefault g) => GenericHasDefault (f :+: g) where
  genericDef = L1 (genericDef @f)
instance GenericHasDefault U1 where
  genericDef = U1 -- unit constructor
instance (Constructor i, GenericHasDefault f) => GenericHasDefault (C1 i f) where
  genericDef = M1 (genericDef @f)
instance (Datatype i, GenericHasDefault f) => GenericHasDefault (D1 i f) where
  genericDef = M1 (genericDef @f)
instance (Selector i, GenericHasDefault f) => GenericHasDefault (S1 i f) where
  genericDef = M1 (genericDef @f)

#ifdef LARGE_RECORDS

instance (LG.Generic a, LG.Constraints a HasDefault) => GenericHasDefault (LG.ThroughLRGenerics a) where
  genericDef = LG.WrapThroughLRGenerics $ LG.to $ LG.cpure (Proxy @HasDefault) (pure def)

#endif

-- | This class captures those types whose names need to appear in .proto files.
--
-- It has a default implementation for any data type which is an instance of the
-- 'Generic' class, which will extract the name of the type constructor.
class Named a where
  -- | Get the name of a type constructor
  nameOf :: IsString string => Proxy# a -> string

  default nameOf :: (IsString string, GenericNamed (Rep a)) => Proxy# a -> string
  nameOf _ = genericNameOf (proxy# :: Proxy# (Rep a))

class GenericNamed (f :: Type -> Type) where
  genericNameOf :: IsString string => Proxy# f -> string

instance Datatype d => GenericNamed (M1 D d f) where
  genericNameOf _ = fromString (datatypeName (undefined :: M1 D d f ()))

instance NameOfWrapperFor a => Named (Wrapped a) where
  nameOf _ = nameOfWrapperFor @a
  {-# INLINE nameOf #-}

-- | Defines the name to be returned by @`HsProtobuf.Named` (`Wrapped` a)@.
class NameOfWrapperFor a where
  nameOfWrapperFor :: forall string . IsString string => string

instance NameOfWrapperFor Double where
  nameOfWrapperFor = "DoubleValue"

instance NameOfWrapperFor Float where
  nameOfWrapperFor = "FloatValue"

instance NameOfWrapperFor Int64 where
  nameOfWrapperFor = "Int64Value"

instance NameOfWrapperFor Word64 where
  nameOfWrapperFor = "UInt64Value"

instance NameOfWrapperFor Int32 where
  nameOfWrapperFor = "Int32Value"

instance NameOfWrapperFor Word32 where
  nameOfWrapperFor = "UInt32Value"

instance NameOfWrapperFor Bool where
  nameOfWrapperFor = "BoolValue"

instance NameOfWrapperFor (Proto3.Suite.Types.String a) where
  nameOfWrapperFor = "StringValue"

instance NameOfWrapperFor (Proto3.Suite.Types.Bytes a) where
  nameOfWrapperFor = "BytesValue"

-- | Enumerable types with finitely many values.
--
-- This class can be derived whenever a sum type is an instance of 'Generic',
-- and only consists of zero-argument constructors. The derived instance should
-- be compatible with `ProtoEnum` instances, in the sense that
--
-- > map (fromJust . toProtoEnumMay . snd) enumerate
--
-- should enumerate all values of the type without runtime errors.
class ProtoEnum a => Finite a where
  -- | Enumerate values of a finite type, along with names of constructors.
  enumerate :: IsString string => Proxy# a -> [(string, Int32)]

  default enumerate ::
    (IsString string, Generic a, GenericFinite (Rep a)) =>
    Proxy# a -> [(string, Int32)]
  enumerate _ =
    fmap (fromProtoEnum . (to :: Rep a p -> a)) <$> genericEnumerate

-- | Generate metadata for an enum type.
enum :: (Finite e, Named e) => Proxy# e -> DotProtoDefinition
enum pr = DotProtoEnum "" (Single $ nameOf pr) (map enumField $ enumerate pr)
  where
    enumField (name, value) = DotProtoEnumField (Single name) value []

class GenericFinite (f :: Type -> Type) where
  genericEnumerate :: IsString string => [(string, f p)]

instance ( GenericFinite f
         , GenericFinite g
         ) => GenericFinite (f :+: g) where
  genericEnumerate =
    (fmap L1 <$> genericEnumerate) <>
    (fmap R1 <$> genericEnumerate)

instance Constructor c => GenericFinite (M1 C c U1) where
  genericEnumerate = [ (fromString name, M1 U1) ]
    where
      name = conName (undefined :: M1 C c f ())

instance GenericFinite f => GenericFinite (M1 D t f) where
  genericEnumerate = fmap M1 <$> genericEnumerate

-- | This class captures those types which correspond to primitives in
-- the protocol buffers specification.
--
-- It should be possible to fully reconstruct values of these types from
-- a single 'RawPrimitive'. Notably, then, `Nested` is not `Primitive` even
-- though it can be 'embedded', since a nested message may by split up over
-- multiple 'embedded' fields.
class Primitive a where
  -- | Encode a primitive value
  encodePrimitive :: FieldNumber -> a -> Encode.MessageBuilder
  -- | Decode a primitive value
  decodePrimitive :: Parser RawPrimitive a
  -- | Get the type which represents this type inside another message.
  primType :: Proxy# a -> DotProtoPrimType

  default primType :: Named a => Proxy# a -> DotProtoPrimType
  primType pr = Named (Single (nameOf pr))

-- | Serialize a message as a lazy 'BL.ByteString'.
toLazyByteString :: Message a => a -> BL.ByteString
toLazyByteString = Encode.toLazyByteString . encodeMessage (fieldNumber 1)

-- | Parse any message that can be decoded.
fromByteString :: Message a => B.ByteString -> Either ParseError a
fromByteString = Decode.parse (decodeMessage (fieldNumber 1))

-- | As 'fromByteString', except the input bytestring is base64-encoded.
fromB64 :: Message a => B.ByteString -> Either ParseError a
fromB64 = fromByteString . B64.decodeLenient

-- | Like `coerce` but lets you avoid specifying a type constructor
-- (such a as parser) that is common to both the input and output types.
coerceOver :: forall a b f . Coercible (f a) (f b) => f a -> f b
coerceOver = coerce

-- | Like `unsafeCoerce` but lets you avoid specifying a type constructor
-- (such a as parser) that is common to both the input and output types.
unsafeCoerceOver :: forall a b f . f a -> f b
unsafeCoerceOver = unsafeCoerce

instance Primitive Int32 where
  encodePrimitive !num = Encode.int32 num
  {-# INLINE encodePrimitive #-}
  decodePrimitive = Decode.int32
  primType _ = Int32

instance Primitive Int64 where
  encodePrimitive !num = Encode.int64 num
  {-# INLINE encodePrimitive #-}
  decodePrimitive = Decode.int64
  primType _ = Int64

instance Primitive Word32 where
  encodePrimitive !num = Encode.uint32 num
  {-# INLINE encodePrimitive #-}
  decodePrimitive = Decode.uint32
  primType _ = UInt32

instance Primitive Word64 where
  encodePrimitive !num = Encode.uint64 num
  {-# INLINE encodePrimitive #-}
  decodePrimitive = Decode.uint64
  primType _ = UInt64

instance Primitive (Signed Int32) where
  encodePrimitive !num = Encode.sint32 num . coerce
  {-# INLINE encodePrimitive #-}
  decodePrimitive = coerce Decode.sint32
  primType _ = SInt32

instance Primitive (Signed Int64) where
  encodePrimitive !num = Encode.sint64 num . coerce
  {-# INLINE encodePrimitive #-}
  decodePrimitive = coerce Decode.sint64
  primType _ = SInt64

instance Primitive (Fixed Word32) where
  encodePrimitive !num = Encode.fixed32 num . coerce
  {-# INLINE encodePrimitive #-}
  decodePrimitive = coerce Decode.fixed32
  primType _ = Fixed32

instance Primitive (Fixed Word64) where
  encodePrimitive !num = Encode.fixed64 num . coerce
  {-# INLINE encodePrimitive #-}
  decodePrimitive = coerce Decode.fixed64
  primType _ = Fixed64

instance Primitive (Signed (Fixed Int32)) where
  encodePrimitive !num = Encode.sfixed32 num . coerce
  {-# INLINE encodePrimitive #-}
  decodePrimitive = coerce Decode.sfixed32
  primType _ = SFixed32

instance Primitive (Signed (Fixed Int64)) where
  encodePrimitive !num = Encode.sfixed64 num . coerce
  {-# INLINE encodePrimitive #-}
  decodePrimitive = coerce Decode.sfixed64
  primType _ = SFixed64

instance Primitive Bool where
  encodePrimitive !num = Encode.bool num
  {-# INLINE encodePrimitive #-}
  decodePrimitive = Decode.bool
  primType _ = Bool

instance Primitive Float where
  encodePrimitive !num = Encode.float num
  {-# INLINE encodePrimitive #-}
  decodePrimitive = Decode.float
  primType _ = Float

instance Primitive Double where
  encodePrimitive !num = Encode.double num
  {-# INLINE encodePrimitive #-}
  decodePrimitive = Decode.double
  primType _ = Double

instance Primitive T.Text where
  encodePrimitive !fn = Encode.text fn . TL.fromStrict
  {-# INLINE encodePrimitive #-}
  decodePrimitive = fmap TL.toStrict Decode.text
  primType _ = String

deriving via T.Text instance Primitive (Proto3.Suite.Types.String T.Text)

instance Primitive TL.Text where
  encodePrimitive !num = Encode.text num
  {-# INLINE encodePrimitive #-}
  decodePrimitive = Decode.text
  primType _ = String

deriving via TL.Text instance Primitive (Proto3.Suite.Types.String TL.Text)

instance Primitive TS.ShortText where
  encodePrimitive !num = Encode.shortText num
  {-# INLINE encodePrimitive #-}
  decodePrimitive = Decode.shortText
  primType _ = String

deriving via TS.ShortText instance Primitive (Proto3.Suite.Types.String TS.ShortText)

instance Primitive B.ByteString where
  encodePrimitive !num = Encode.byteString num
  {-# INLINE encodePrimitive #-}
  decodePrimitive = Decode.byteString
  primType _ = Bytes

deriving via B.ByteString instance Primitive (Proto3.Suite.Types.Bytes B.ByteString)

instance Primitive BL.ByteString where
  encodePrimitive !num = Encode.lazyByteString num
  {-# INLINE encodePrimitive #-}
  decodePrimitive = Decode.lazyByteString
  primType _ = Bytes

deriving via BL.ByteString instance Primitive (Proto3.Suite.Types.Bytes BL.ByteString)

instance forall e. (Named e, ProtoEnum e) => Primitive (Enumerated e) where
  encodePrimitive !num = either (Encode.int32 num) (Encode.enum num) . enumerated
  {-# INLINE encodePrimitive #-}
  decodePrimitive = coerce
    @(Parser RawPrimitive (Either Int32 e))
    @(Parser RawPrimitive (Enumerated e))
    Decode.enum
  primType _ = Named (Single (nameOf (proxy# :: Proxy# e)))

instance (Primitive a) => Primitive (ForceEmit a) where
  encodePrimitive !num = encodePrimitive num . forceEmit
  {-# INLINE encodePrimitive #-}
  decodePrimitive     = coerce @(Parser RawPrimitive a) @(Parser RawPrimitive (ForceEmit a)) decodePrimitive
  primType _          = primType (proxy# :: Proxy# a)

-- | This class captures those types which can appear as message fields in
-- the protocol buffers specification, i.e. 'Primitive' types, or lists of
-- 'Primitive' types
class MessageField a where
  -- | Encode a message field
  encodeMessageField :: FieldNumber -> a -> Encode.MessageBuilder
  -- | Decode a message field
  decodeMessageField :: Parser RawField a

  default encodeMessageField :: (HasDefault a, Primitive a)
                             => FieldNumber -> a -> Encode.MessageBuilder
  encodeMessageField !num x
    | isDefault x = mempty
    | otherwise = encodePrimitive num x
  {-# INLINE encodeMessageField #-}

  default decodeMessageField :: (HasDefault a, Primitive a) => Parser RawField a
  decodeMessageField = one decodePrimitive def

  -- | Get the type which represents this type inside another message.
  protoType :: Proxy# a -> DotProtoField
  default protoType :: Primitive a => Proxy# a -> DotProtoField
  protoType p = messageField (Prim $ primType p) Nothing

messageField :: DotProtoType -> Maybe Packing -> DotProtoField
messageField ty packing = DotProtoField
    { dotProtoFieldNumber = fieldNumber 1
    , dotProtoFieldType = ty
    , dotProtoFieldName = Anonymous
    , dotProtoFieldOptions = packingOption
    , dotProtoFieldComment = ""
    }
  where
    packingOption = maybe [] (toDotProtoOption . isPacked) packing

    toDotProtoOption b = [DotProtoOption (Single "packed") (BoolLit b)]

    isPacked PackedField   = True
    isPacked UnpackedField = False

instance MessageField Int32
instance MessageField Int64
instance MessageField Word32
instance MessageField Word64
instance MessageField (Signed Int32)
instance MessageField (Signed Int64)
instance MessageField (Fixed Word32)
instance MessageField (Fixed Word64)
instance MessageField (Signed (Fixed Int32))
instance MessageField (Signed (Fixed Int64))
instance MessageField Bool
instance MessageField Float
instance MessageField Double
instance MessageField T.Text
deriving via T.Text instance MessageField (Proto3.Suite.Types.String T.Text)
instance MessageField TL.Text
deriving via TL.Text instance MessageField (Proto3.Suite.Types.String TL.Text)
instance MessageField TS.ShortText
deriving via TS.ShortText instance MessageField (Proto3.Suite.Types.String TS.ShortText)
instance MessageField B.ByteString
deriving via B.ByteString instance MessageField (Proto3.Suite.Types.Bytes B.ByteString)
instance MessageField BL.ByteString
deriving via BL.ByteString instance MessageField (Proto3.Suite.Types.Bytes BL.ByteString)
instance (Named e, ProtoEnum e) => MessageField (Enumerated e)

instance (Ord k, Primitive k, MessageField k, Primitive v, MessageField v) => MessageField (M.Map k v) where
  encodeMessageField !num = go op
    where
      go f = foldMap f . M.toList
      op = Encode.embedded num . encodeMessage (fieldNumber 1)
      {-# INLINABLE op #-}  -- To allow specialization to a particular type class or field number.
  {-# INLINE encodeMessageField #-}

  -- Data.Map.fromList will retain the last key/value mapping. From the spec:
  --
  -- > When parsing from the wire or when merging, if there are duplicate map
  -- > keys the last key seen is used.
  decodeMessageField = M.fromList . Foldable.toList
                       <$> repeated (Decode.embedded' (decodeMessage (fieldNumber 1)))
  protoType _ = messageField (Map (primType (proxy# :: Proxy# k)) (primType (proxy# :: Proxy# v))) Nothing

instance {-# OVERLAPS #-} (Ord k, Primitive k, Named v, Message v, MessageField k) => MessageField (M.Map k (Nested v)) where
  encodeMessageField !num = go op
    where
      go f = foldMap f . M.toList
      op = Encode.embedded num . encodeMessage (fieldNumber 1)
      {-# INLINABLE op #-}  -- To allow specialization to a particular type class or field number.
  {-# INLINE encodeMessageField #-}

  -- Data.Map.fromList will retain the last key/value mapping. From the spec:
  --
  -- > When parsing from the wire or when merging, if there are duplicate map
  -- > keys the last key seen is used.
  decodeMessageField = M.fromList . Foldable.toList
                       <$> repeated (Decode.embedded' (decodeMessage (fieldNumber 1)))
  protoType _ = messageField (Map (primType (proxy# :: Proxy# k)) (Named . Single $ nameOf (proxy# :: Proxy# v))) Nothing

instance (HasDefault a, Primitive a) => MessageField (ForceEmit a) where
  encodeMessageField !num = encodePrimitive num
  {-# INLINE encodeMessageField #-}

instance (Named a, Message a) => MessageField (Nested a) where
  encodeMessageField !num = go op
    where
      go f = foldMap f . coerce @(Nested a) @(Maybe a)
      op = Encode.embedded num . encodeMessage (fieldNumber 1)
      {-# INLINABLE op #-}  -- To allow specialization to a particular type class or field number.
  {-# INLINE encodeMessageField #-}
  decodeMessageField = coerce @(Parser RawField (Maybe a)) @(Parser RawField (Nested a))
                       (Decode.embedded (decodeMessage (fieldNumber 1)))
  protoType _ = messageField (Prim . Named . Single $ nameOf (proxy# :: Proxy# a)) Nothing

instance Primitive a => MessageField (UnpackedVec a) where
  encodeMessageField !fn = go op
    where
      go f = Encode.vectorMessageBuilder f . unpackedvec
      op = encodePrimitive fn
      {-# INLINABLE op #-}  -- To allow specialization to a particular type class or field number.
  {-# INLINE encodeMessageField #-}
  decodeMessageField =
    UnpackedVec . fromList . Foldable.toList <$> repeated decodePrimitive
  protoType _ = messageField (Repeated $ primType (proxy# :: Proxy# a)) (Just UnpackedField)

instance forall a. (Named a, Message a) => MessageField (NestedVec a) where
  encodeMessageField !fn = go op
    where
      go f = Encode.vectorMessageBuilder f . nestedvec
      op = Encode.embedded fn . encodeMessage (fieldNumber 1)
      {-# INLINABLE op #-}  -- To allow specialization to a particular type class or field number.
  {-# INLINE encodeMessageField #-}
  decodeMessageField =
      fmap (coerce @(Vector a) @(NestedVec a) . fromList . Foldable.toList)
           (repeated (Decode.embedded' oneMsg))
    where
      oneMsg :: Parser RawMessage a
      oneMsg = decodeMessage (fieldNumber 1)
  protoType _ = messageField (NestedRepeated . Named . Single $ nameOf (proxy# :: Proxy# a)) Nothing

instance (Named e, ProtoEnum e) => MessageField (PackedVec (Enumerated e)) where
  encodeMessageField !fn =
    omittingDefault (Encode.packedVarintsV (fromIntegral . codeFromEnumerated) fn)
    . packedvec
  {-# INLINE encodeMessageField #-}  -- Let 'Encode.packedVarintsV' figure out how much to inline.
  decodeMessageField = decodePacked (map (codeToEnumerated . fromIntegral) <$> Decode.packedVarints @Word64)
  protoType _ = messageField (Repeated . Named . Single $ nameOf (proxy# :: Proxy# e)) (Just PackedField)

instance MessageField (PackedVec Bool) where
  encodeMessageField !fn = omittingDefault (Encode.packedBoolsV id fn) . packedvec
  {-# INLINE encodeMessageField #-}  -- Let 'Encode.packedBoolsV' figure out how much to inline.
  decodeMessageField = fmap (fmap toBool) (decodePacked Decode.packedVarints)
    where
      toBool :: Word64 -> Bool
      toBool 1 = True
      toBool _ = False
  protoType _ = messageField (Repeated Bool) (Just PackedField)

instance MessageField (PackedVec Word32) where
  encodeMessageField !fn = omittingDefault (Encode.packedVarintsV fromIntegral fn) . packedvec
  {-# INLINE encodeMessageField #-}  -- Let 'Encode.packedVarintsV' figure out how much to inline.
  decodeMessageField = decodePacked Decode.packedVarints
  protoType _ = messageField (Repeated UInt32) (Just PackedField)

instance MessageField (PackedVec Word64) where
  encodeMessageField !fn = omittingDefault (Encode.packedVarintsV id fn) . packedvec
  {-# INLINE encodeMessageField #-}  -- Let 'Encode.packedVarintsV' figure out how much to inline.
  decodeMessageField = decodePacked Decode.packedVarints
  protoType _ = messageField (Repeated UInt64) (Just PackedField)

instance MessageField (PackedVec Int32) where
  encodeMessageField !fn = omittingDefault (Encode.packedVarintsV fromIntegral fn) . packedvec
  {-# INLINE encodeMessageField #-}  -- Let 'Encode.packedVarintsV' figure out how much to inline.
  decodeMessageField = decodePacked Decode.packedVarints
  protoType _ = messageField (Repeated Int32) (Just PackedField)

instance MessageField (PackedVec Int64) where
  encodeMessageField !fn = omittingDefault (Encode.packedVarintsV fromIntegral fn) . packedvec
  {-# INLINE encodeMessageField #-}  -- Let 'Encode.packedVarintsV' figure out how much to inline.
  decodeMessageField = decodePacked Decode.packedVarints
  protoType _ = messageField (Repeated Int64) (Just PackedField)

instance MessageField (PackedVec (Signed Int32)) where
  encodeMessageField !fn =
      omittingDefault (Encode.packedVarintsV zigZag fn) . coerce @_ @(Vector Int32)
    where
      zigZag = fromIntegral . Encode.zigZagEncode
  {-# INLINE encodeMessageField #-}  -- Let 'Encode.packedVarintsV' figure out how much to inline.

  decodeMessageField = decodePacked (fmap (fmap zagZig) Decode.packedVarints)
    where
      -- This type signature is important: `Decode.zigZagDecode` will not undo
      -- `Encode.zigZagEncode` if given a signed value with the high order bit
      -- set. So we don't allow GHC to infer a signed input type.
      zagZig :: Word32 -> Signed Int32
      zagZig = Signed . fromIntegral . Decode.zigZagDecode

  protoType _ = messageField (Repeated SInt32) (Just PackedField)

instance MessageField (PackedVec (Signed Int64)) where
  encodeMessageField !fn =
      omittingDefault (Encode.packedVarintsV zigZag fn) . coerce @_ @(Vector Int64)
    where
      zigZag = fromIntegral . Encode.zigZagEncode
  {-# INLINE encodeMessageField #-}  -- Let 'Encode.packedVarintsV' figure out how much to inline.

  decodeMessageField = decodePacked (fmap (fmap zagZig) Decode.packedVarints)
    where
      -- This type signature is important: `Decode.zigZagDecode` will not undo
      -- `Encode.zigZagEncode` if given a signed value with the high order bit
      -- set. So we don't allow GHC to infer a signed input type.
      zagZig :: Word64 -> Signed Int64
      zagZig = Signed . fromIntegral . Decode.zigZagDecode

  protoType _ = messageField (Repeated SInt64) (Just PackedField)


instance MessageField (PackedVec (Fixed Word32)) where
  encodeMessageField !fn =
    omittingDefault (Encode.packedFixed32V id fn) . coerce @_ @(Vector Word32)
  {-# INLINE encodeMessageField #-}  -- Let 'Encode.packedFixed32V' figure out how much to inline.
  decodeMessageField = coerce @(Parser RawField (PackedVec Word32))
                              @(Parser RawField (PackedVec (Fixed Word32)))
                              (decodePacked Decode.packedFixed32)
  protoType _ = messageField (Repeated Fixed32) (Just PackedField)

instance MessageField (PackedVec (Fixed Word64)) where
  encodeMessageField !fn =
    omittingDefault (Encode.packedFixed64V id fn) . coerce @_ @(Vector Word64)
  {-# INLINE encodeMessageField #-}  -- Let 'Encode.packedFixed64V' figure out how much to inline.
  decodeMessageField = coerce @(Parser RawField (PackedVec Word64))
                              @(Parser RawField (PackedVec (Fixed Word64)))
                              (decodePacked Decode.packedFixed64)
  protoType _ = messageField (Repeated Fixed64) (Just PackedField)

instance MessageField (PackedVec (Signed (Fixed Int32))) where
  encodeMessageField !fn =
    omittingDefault (Encode.packedFixed32V fromIntegral fn) . coerce @_ @(Vector Int32)
  {-# INLINE encodeMessageField #-}  -- Let 'Encode.packedFixed32V' figure out how much to inline.
  decodeMessageField = coerce @(Parser RawField (PackedVec Int32))
                              @(Parser RawField (PackedVec (Signed (Fixed Int32))))
                             (decodePacked Decode.packedFixed32)
  protoType _ = messageField (Repeated SFixed32) (Just PackedField)

instance MessageField (PackedVec (Signed (Fixed Int64))) where
  encodeMessageField !fn =
    omittingDefault (Encode.packedFixed64V fromIntegral fn) . coerce @_ @(Vector Int64)
  {-# INLINE encodeMessageField #-}  -- Let 'Encode.packedFixed64V' figure out how much to inline.
  decodeMessageField = coerce @(Parser RawField (PackedVec Int64))
                              @(Parser RawField (PackedVec (Signed (Fixed Int64))))
                              (decodePacked Decode.packedFixed64)
  protoType _ = messageField (Repeated SFixed64) (Just PackedField)

instance MessageField (PackedVec Float) where
  encodeMessageField !fn = omittingDefault (Encode.packedFloatsV id fn) . packedvec
  {-# INLINE encodeMessageField #-}  -- Let 'Encode.packedFloatsV' figure out how much to inline.
  decodeMessageField = decodePacked Decode.packedFloats
  protoType _ = messageField (Repeated Float) (Just PackedField)

instance MessageField (PackedVec Double) where
  encodeMessageField !fn = omittingDefault (Encode.packedDoublesV id fn) . packedvec
  {-# INLINE encodeMessageField #-}  -- Let 'Encode.packedDoublesV' figure out how much to inline.
  decodeMessageField = decodePacked Decode.packedDoubles
  protoType _ = messageField (Repeated Double) (Just PackedField)

instance (MessageField e, KnownSymbol comments) => MessageField (e // comments) where
  encodeMessageField !fn = encodeMessageField fn . unCommented
  {-# INLINE encodeMessageField #-}
  decodeMessageField = coerce @(Parser RawField e)
                              @(Parser RawField (Commented comments e))
                              decodeMessageField
  protoType p = (protoType (lowerProxy1 p))
                  { dotProtoFieldComment = symbolVal (lowerProxy2 p) }
    where
      lowerProxy1 :: forall k f (a :: k). Proxy# (f a) -> Proxy# a
      lowerProxy1 _ = proxy#

      lowerProxy2 :: forall k f (a :: k) b. Proxy# (f a b) -> Proxy a
      lowerProxy2 _ = Proxy

decodePacked
  :: Parser RawPrimitive [a]
  -> Parser RawField (PackedVec a)
decodePacked = Parser
             . fmap (fmap (pack . Foldable.toList))
             . TR.traverse
             . runParser
  where
    pack :: forall a. [[a]] -> PackedVec a
    pack = fromList . join . reverse


-- | This class captures those types which correspond to protocol buffer messages.
class Message a where
  -- | Encode a message
  encodeMessage :: FieldNumber -> a -> Encode.MessageBuilder
  -- | Decode a message
  decodeMessage :: FieldNumber -> Parser RawMessage a
  -- | Generate a .proto message from the type information.
  dotProto :: Proxy# a -> [DotProtoField]

  default encodeMessage :: (Generic a, GenericMessage (Rep a))
                        => FieldNumber -> a -> Encode.MessageBuilder
  encodeMessage num = genericEncodeMessage num . from

  default decodeMessage :: (Generic a, GenericMessage (Rep a))
                        => FieldNumber -> Parser RawMessage a
  decodeMessage = fmap to . genericDecodeMessage

  default dotProto :: GenericMessage (Rep a)
                   => Proxy# a -> [DotProtoField]
  dotProto _ = genericDotProto (proxy# :: Proxy# (Rep a))

instance (MessageField k, MessageField v) => Message (k, v)

instance (MessageField a, Primitive a) => Message (Wrapped a) where
  encodeMessage _ (Wrapped v) = encodeMessageField (FieldNumber 1) v
  {-# INLINABLE encodeMessage #-}
  decodeMessage _ = Wrapped <$> at decodeMessageField (FieldNumber 1)
  {-# INLINABLE decodeMessage #-}
  dotProto _ =
    [ DotProtoField
        (FieldNumber 1)
        (Prim (primType (proxy# :: Proxy# a)))
        (Single "value")
        []
        ""
    ]

-- | Generate metadata for a message type.
message :: (Message a, Named a) => Proxy# a -> DotProtoDefinition
message proxy = DotProtoMessage ""
                                (Single $ nameOf proxy)
                                (DotProtoMessageField <$> dotProto proxy)

-- * Wrapped Type Instances

encodeWrapperMessage
  :: MessageField a
  => FieldNumber
  -> a
  -> Encode.MessageBuilder
encodeWrapperMessage _ x = encodeMessageField (FieldNumber 1) x

decodeWrapperMessage
  :: MessageField a
  => FieldNumber
  -> Decode.Parser Decode.RawMessage a
decodeWrapperMessage _ = at decodeMessageField (FieldNumber 1)

dotProtoWrapper :: Primitive a => Proxy# a -> [DotProtoField]
dotProtoWrapper proxy =
  [ DotProtoField
      (FieldNumber 1)
      (Prim (primType proxy))
      (Single "value")
      []
      ""
  ]

instance Message Double where
  encodeMessage = encodeWrapperMessage
  decodeMessage = decodeWrapperMessage
  dotProto = dotProtoWrapper

instance Message Float where
  encodeMessage = encodeWrapperMessage
  decodeMessage = decodeWrapperMessage
  dotProto = dotProtoWrapper

instance Message Int64 where
  encodeMessage = encodeWrapperMessage
  decodeMessage = decodeWrapperMessage
  dotProto = dotProtoWrapper

instance Message Word64 where
  encodeMessage = encodeWrapperMessage
  decodeMessage = decodeWrapperMessage
  dotProto = dotProtoWrapper

instance Message Int32 where
  encodeMessage = encodeWrapperMessage
  decodeMessage = decodeWrapperMessage
  dotProto = dotProtoWrapper

instance Message Word32 where
  encodeMessage = encodeWrapperMessage
  decodeMessage = decodeWrapperMessage
  dotProto = dotProtoWrapper

instance Message Bool where
  encodeMessage = encodeWrapperMessage
  decodeMessage = decodeWrapperMessage
  dotProto = dotProtoWrapper

instance Message T.Text where
  encodeMessage = encodeWrapperMessage
  decodeMessage = decodeWrapperMessage
  dotProto = dotProtoWrapper

deriving via T.Text instance Message (Proto3.Suite.Types.String T.Text)

instance Message TL.Text where
  encodeMessage = encodeWrapperMessage
  decodeMessage = decodeWrapperMessage
  dotProto = dotProtoWrapper

deriving via TL.Text instance Message (Proto3.Suite.Types.String TL.Text)

instance Message TS.ShortText where
  encodeMessage = encodeWrapperMessage
  decodeMessage = decodeWrapperMessage
  dotProto = dotProtoWrapper

deriving via TS.ShortText instance Message (Proto3.Suite.Types.String TS.ShortText)

instance Message B.ByteString where
  encodeMessage = encodeWrapperMessage
  decodeMessage = decodeWrapperMessage
  dotProto = dotProtoWrapper

instance Message BL.ByteString where
  encodeMessage = encodeWrapperMessage
  decodeMessage = decodeWrapperMessage
  dotProto = dotProtoWrapper

-- * Generic Instances

class GenericMessage (f :: Type -> Type) where
  type GenericFieldCount f :: Nat

  genericEncodeMessage :: FieldNumber -> f a -> Encode.MessageBuilder
  genericDecodeMessage :: FieldNumber -> Parser RawMessage (f a)
  genericDotProto      :: Proxy# f -> [DotProtoField]

instance GenericMessage U1 where
  type GenericFieldCount U1 = 0
  genericEncodeMessage _ = mempty
  genericDecodeMessage _ = pure U1
  genericDotProto _      = mempty

instance (KnownNat (GenericFieldCount f), GenericMessage f, GenericMessage g)
           => GenericMessage (f :*: g)
  where
    type GenericFieldCount (f :*: g) = GenericFieldCount f + GenericFieldCount g
    genericEncodeMessage num (x :*: y) =
        genericEncodeMessage num x <>
        genericEncodeMessage (FieldNumber (getFieldNumber num + offset)) y
      where
        offset = fromIntegral $ natVal (Proxy @(GenericFieldCount f))

    genericDecodeMessage num =
        liftA2 (:*:) (genericDecodeMessage num)
                     (genericDecodeMessage num2)
      where
        num2 = FieldNumber $ getFieldNumber num + offset
        offset = fromIntegral $ natVal (Proxy @(GenericFieldCount f))

    genericDotProto _ =
        genericDotProto (proxy# :: Proxy# f) <>
        adjust (genericDotProto (proxy# :: Proxy# g))
      where
        offset = fromIntegral $ natVal (Proxy @(GenericFieldCount f))
        adjust = map adjustPart
        adjustPart part = part
          { dotProtoFieldNumber = FieldNumber . (offset +)
                                  . getFieldNumber . dotProtoFieldNumber
                                  $ part
          }

instance MessageField c => GenericMessage (K1 i c) where
  type GenericFieldCount (K1 i c) = 1
  genericEncodeMessage num (K1 x) = encodeMessageField num x
  genericDecodeMessage num        = K1 <$> at decodeMessageField num
  genericDotProto _               = [protoType (proxy# :: Proxy# c)]

instance (Selector s, GenericMessage f) => GenericMessage (M1 S s f) where
  type GenericFieldCount (M1 S s f) = GenericFieldCount f
  genericEncodeMessage num (M1 x)   = genericEncodeMessage num x
  genericDecodeMessage num          = M1 <$> genericDecodeMessage num
  genericDotProto _                 = map applyName $ genericDotProto (proxy# :: Proxy# f)
    where
      applyName :: DotProtoField -> DotProtoField
      applyName mp = mp { dotProtoFieldName = fromMaybe Anonymous newName}
      -- [issue] this probably doesn't match the intended name generating semantics

      newName :: Maybe DotProtoIdentifier
      newName = guard (not (null name)) $> Single name
        where
          name = selName (undefined :: S1 s f ())

instance GenericMessage f => GenericMessage (M1 C t f) where
  type GenericFieldCount (M1 C t f) = GenericFieldCount f
  genericEncodeMessage num (M1 x)   = genericEncodeMessage num x
  genericDecodeMessage num          = M1 <$> genericDecodeMessage num
  genericDotProto _                 = genericDotProto (proxy# :: Proxy# f)

instance GenericMessage f => GenericMessage (M1 D t f) where
  type GenericFieldCount (M1 D t f) = GenericFieldCount f
  genericEncodeMessage num (M1 x)   = genericEncodeMessage num x
  genericDecodeMessage num          = M1 <$> genericDecodeMessage num
  genericDotProto _                 = genericDotProto (proxy# :: Proxy# f)