proto3-suite
proto3-suite copied to clipboard
Issue decoding similar message types
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: 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.
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 FooBar
s, what's the possibility of doing it the other way generically (i.e., Haskell types -> protobuf codecs)?
@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.