proto3-suite icon indicating copy to clipboard operation
proto3-suite copied to clipboard

Issue decoding similar message types

Open IvantheTricourne opened this issue 5 years ago • 3 comments

Say we have 2 message types defined in Haskell and attempt to write codec instances for each:

data Foo = Foo
  { fooVal :: Text
  , fooValToo :: Text
  } deriving (Eq, Show, Generic)

instance Message Foo
instance Named Foo
instance HasCodec Foo

data Bar = Bar
  { barVal :: Text
  } deriving (Eq, Show, Generic)

instance Message DeleteName
instance Named DeleteName
instance HasCodec DeleteName

Let's assume HasCodec is a type class that provides the proper encode/decode functionality for a type. Next, we try to wrap both messages in a sum type that has its own HasCodec type:

data FooBarMessage =
    NFoo Foo
  | NBar Bar
  deriving (Eq, Show, Generic)

instance HasCodec FooBarMessage where
  decode bs =
    fmap NFoo (decode bs) <>
    fmap NBar (decode bs)
  encode = \case
    NFoo msg -> encode msg
    NBar msg -> encode msg

Currently, I have NBar messages being incorrectly decoded as NFoo messages under this context. Is this a known behavior? Or am I perhaps missing/misusing something? Is there any constraint on deriving Generic for sum types?

IvantheTricourne avatar Nov 20 '19 21:11 IvantheTricourne

@IvantheTricourne: Encoded protobuf messages are schema-free, meaning that you cannot necessarily infer from the encoded value which type they should decode into. For example, an empty ByteString will always successfully decode as any protobuf value (with all fields set to the default/empty value).

This implies that you need to know out-of-band which type you expect to decode into rather than inferring which type by trying to decode each candidate type in succession.

Gabriella439 avatar Nov 21 '19 22:11 Gabriella439

Thanks for the reply @Gabriel439

I wrote the equivalent protobuf file for the example HS above:

syntax="proto3";
package TestProtoOneofImport;
message FooBar {
  oneof value {
    Foo foo = 1;
    Bar bar = 2;
  }
}

message Foo {
  string val = 1;
  string val_too = 2;
}

message Bar {
  string val = 1;
}

This has the semantic equivalent of representing a sum type over a set of messages as another message type. In this way, the codecs for each type are simply the bytestring encoders/decoders from the Message type instance. Generating haskell from the above proto snippet, we get this decode definition for the FooBar sum type:

decodeMessage _
          = (Hs.pure FooBar) <*>
              (HsProtobuf.oneof Hs.Nothing
                 [((HsProtobuf.FieldNumber 1),
                   (Hs.pure (Hs.fmap FooBarValueFoo)) <*>
                     (Hs.coerce @(_ (HsProtobuf.Nested Test.Foo))
                        @(_ (Hs.Maybe Test.Foo))
                        HsProtobuf.decodeMessageField)),
                  ((HsProtobuf.FieldNumber 2),
                   (Hs.pure (Hs.fmap FooBarValueBar)) <*>
                     (Hs.coerce @(_ (HsProtobuf.Nested Test.Bar))
                        @(_ (Hs.Maybe Test.Bar))
                        HsProtobuf.decodeMessageField))])

So, assuming this haskell snippet does successfully decode FooBars, what's the possibility of doing it the other way generically (i.e., Haskell types -> protobuf codecs)?

IvantheTricourne avatar Nov 22 '19 21:11 IvantheTricourne

@IvantheTricourne: This package supports deriving instances from Haskell types using GHC generics (i.e. deriving (Generic, Message)), but that does not yet support sum types. However, as far as I know there's no particular reason we don't yet support deriving the encoder/decoder sum types: we just never used it up until now so we haven't implemented it yet.

Gabriella439 avatar Nov 25 '19 23:11 Gabriella439