Skip to content

Commit

Permalink
Fixing bug with decoding empty lists in protobuf codec. (issue zio#94) (
Browse files Browse the repository at this point in the history
zio#140)

* Fixing bug with decoding empty lists in protobuf code. (issue zio#94)

* Refactor. (issue zio#94)
  • Loading branch information
kurgansoft authored and landlockedsurfer committed May 28, 2022
1 parent a4a14d6 commit 3b132ce
Show file tree
Hide file tree
Showing 2 changed files with 55 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -349,7 +349,10 @@ object ProtobufCodec extends Codec {
}.getOrElse(Chunk.empty)
}

final case class Decoder[+A](run: Chunk[Byte] => Either[String, (Chunk[Byte], A)]) {
final case class Decoder[+A](
run: Chunk[Byte] => Either[String, (Chunk[Byte], A)],
handlesEmptyChunk: Boolean = false
) {
self =>

def map[B](f: A => B): Decoder[B] =
Expand All @@ -362,7 +365,7 @@ object ProtobufCodec extends Codec {

def flatMap[B](f: A => Decoder[B]): Decoder[B] =
Decoder { bytes =>
if (bytes.isEmpty) {
if (bytes.isEmpty && !handlesEmptyChunk) {
Left("Unexpected end of bytes")
} else {
self.run(bytes).flatMap {
Expand Down Expand Up @@ -422,8 +425,24 @@ object ProtobufCodec extends Codec {
private[codec] def decoder[A](schema: Schema[A]): Decoder[A] =
schema match {
case Schema.GenericRecord(structure) => recordDecoder(structure.toChunk)
case Schema.Sequence(element, f, _) =>
if (canBePacked(element)) packedSequenceDecoder(element).map(f) else nonPackedSequenceDecoder(element).map(f)
case Schema.Sequence(elementSchema @ Schema.Sequence(_, _, _), fromChunk, _) =>
if (canBePacked(elementSchema)) packedSequenceDecoder(elementSchema).map(fromChunk)
else nonPackedSequenceDecoder(elementSchema).map(fromChunk)
case Schema.Sequence(elementSchema, fromChunk, _) =>
Decoder[A](
{ bytes =>
{
if (bytes.isEmpty)
Right((Chunk.empty, fromChunk(Chunk.empty)))
else if (canBePacked(elementSchema)) {
packedSequenceDecoder(elementSchema).map(fromChunk).run(bytes)
} else {
nonPackedSequenceDecoder(elementSchema).map(fromChunk).run(bytes)
}
}
},
true
)
case Schema.Transform(codec, f, _) => transformDecoder(codec, f)
case Schema.Primitive(standardType) => primitiveDecoder(standardType)
case Schema.Tuple(left, right) => tupleDecoder(left, right)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,24 @@ object ProtobufCodecSpec extends DefaultRunnableSpec {
}
),
suite("Should successfully encode and decode")(
testM("empty list") {
for {
ed <- encodeAndDecodeNS(DeriveSchema.gen[List[Int]], List.empty)
} yield assert(ed)(equalTo(List.empty))
},
testM("list of an empty list") {
for {
ed <- encodeAndDecodeNS(DeriveSchema.gen[List[List[Int]]], List(List.empty))
} yield assert(ed)(equalTo(List(List.empty)))
},
testM("tuple containing empty list & tuple containing list of an empty list") {
val value: (String, List[List[Int]], String) = ("first string", List(List.empty), "second string")
val value2: (String, List[Int], String) = ("first string", List.empty, "second string")
for {
ed <- encodeAndDecodeNS(DeriveSchema.gen[(String, List[List[Int]], String)], value)
ed2 <- encodeAndDecodeNS(DeriveSchema.gen[(String, List[Int], String)], value2)
} yield assert(ed)(equalTo(value)) && assert(ed2)(equalTo(value2))
},
testM("records") {
for {
ed2 <- encodeAndDecodeNS(Record.schemaRecord, Record("hello", 150))
Expand Down Expand Up @@ -309,13 +327,27 @@ object ProtobufCodecSpec extends DefaultRunnableSpec {
ed2 <- encodeAndDecodeNS(schemaPackedList, list)
} yield assert(ed)(equalTo(Chunk(list))) && assert(ed2)(equalTo(list))
},
testM("empty packed sequence") {
val list = PackedList(List.empty)
for {
ed <- encodeAndDecode(schemaPackedList, list)
ed2 <- encodeAndDecodeNS(schemaPackedList, list)
} yield assert(ed)(equalTo(Chunk(list))) && assert(ed2)(equalTo(list))
},
testM("non-packed sequences") {
val list = UnpackedList(List("foo", "bar", "baz"))
for {
ed <- encodeAndDecode(schemaUnpackedList, list)
ed2 <- encodeAndDecodeNS(schemaUnpackedList, list)
} yield assert(ed)(equalTo(Chunk(list))) && assert(ed2)(equalTo(list))
},
testM("empty non-packed sequence") {
val list = UnpackedList(List.empty)
for {
ed <- encodeAndDecode(schemaUnpackedList, list)
ed2 <- encodeAndDecodeNS(schemaUnpackedList, list)
} yield assert(ed)(equalTo(Chunk(list))) && assert(ed2)(equalTo(list))
},
testM("enumerations") {
for {
ed <- encodeAndDecode(schemaEnumeration, Enumeration(BooleanValue(true)))
Expand Down

0 comments on commit 3b132ce

Please sign in to comment.